Domain-Driven Design in Nuxt Apps

Part 1 of 1 in our Modular Vue.js Applications series.
Written by Filip Rakowski

I’ve been experimenting with Domain-Driven approach in Vue apps for quite some time already in Vue Storefront and Vue Storefront Next. Utilizing this approach can lead to significant improvement in areas related to the maintenance and complexity of your codebase. In this mini-series I want to share some of the patterns that worked for us in Vue Storefront and can be easily applied in any Vue application.

I will show how to apply these patterns both in Nuxt and β€œvanilla” Vue projects. In this article we will focus on theory and Nuxt implementation.

What is Domain-Driven Design?

Domain-Driven Design (DDD) is the concept that prioritizes business concepts over other types of classification in your codebase (like grouping by file type). It means that you should structure and group your code based on your main business domain (the β€œproblem”) and its subdomains (categorized parts of the problem). For example within the eCommerce domain we can have subdomains such as product catalog, customers, orders, inventory etc.

While the DDD concept comes from Object-Oriented Programming and relies on classes and their relations its core concepts can be easily applied in any other paradigm.

Why is Domain-Driven Design great?

The simplest explanation of DDD gaining more and more popularity is that it’s a dead-simple pattern that in most cases leads to significant improvements in one of the hardest and most painful areas of programming - code maintainability.

I remember all these sleepless nights and long showers when I was thinking about the best way of structuring my repo. If you ever had to figure that out, you know very well that this task is extremely hard, and the result is never perfect because you have to take into account ALL the possible ways your project could evolve into in the future and that's simply not possible. Over time our understanding of the problems we’re trying to solve grows which leads to more efficient ways of solving them and therefore - changing our initial approach.

We’ve all been there and we know how costly changing the initial architecture could be.

This is where DDD shines. It’s much easier to model the system based on business requirements because this approach is not biased by technical limitations and difficulties. We change our core business requirements much less often than low-level technicals.

Moreover DDD embraces writing code with the actual business in mind, which means that we organize our code and folder structures on the business features, such as customers instead of technical terms (components, stores, services). This way of managing code makes it much easier to maintain it.

The images below illustrate a β€œstandard” Vue folder structure vs the one based on the business domain:

Learn Vue.js With Vue School

If everything related to a certain feature is in one place it’s easy to understand how it works because there is no risk of overlooking some parts. It’s equally easy to fully remove a feature because its code is not distributed across the whole codebase. Another benefit would be the fact that we can easily reason about the business part of our application and its features just by looking at its folder structure. It is also much easier to find a place we have to work on because our tasks are in most cases written in a business language. I could give many more examples, but I’m sure you already get why this approach can be perceived as more maintainable by now.

One of the biggest benefits of DDD and grouping your code by features is limiting the number of connections between different modules. Usually we have a set of extension points in our app used by all modules. Ideally all the connections should happen through a single place like a shared module or event bus. With this approach its easier to maintain such modules because connecting different features is usually the part that leads to the biggest increase in apps complexity. If we manage to avoid unnecessary complexity and make the connections simple it’s fairly easy to keep your codebase simple as well.

Figuring out how to make your modules independent and encapsulated is a topic for another article (or even a series of them) so for now if this is something that you want to dig deeper into I suggest reading about the hexagonal architecture and applying the most useful concepts from this approach.

So how we can apply Domain-Driven Design concept in Vue?

Domain-Driven Design in Nuxt

Let’s start with Nuxt, which already has a great built-in mechanism that makes it extremely easy to apply this concept - modules.

Nuxt modules are commonly used to include third-party functionalities like authorization or i18n into your app. While authoring third-party code is the most common way of utilizing them Nuxt modules can also serve very well in our internal architecture.

Because Nuxt Module can hook into different parts of your application like routing, or the Vuex store we can easily make it the only place from which a particular subdomain is connecting with our application!

Take a look at this simple Nuxt module:

// index.js
module.exports = function ProductCatalogModule (moduleOptions) {
  this.extendRoutes((routes) => {
    routes.unshift({
      name: 'product',
      path: '/p/:slug/',
      component: path.resolve(themeDir, 'pages/Product.vue')
    });
  );
  // we can't register stores through Nuxt modules so we have to make a plugin
  this.addPlugin(path.resolve(__dirname, 'plugins/add-stores.js'))
}
// plugins/add-stores.js
import CategoryStore from '../stores/category.js'
import ProductStore from '../stores/product.js'

export default async ({ store }) => {
  store.registerModule(CategoryStore)
  store.registerModule(ProductStore)
};

…with following files:

.
β”œβ”€β”€ pages
|   |── Category.vue 
β”‚   └── Product.vue
β”œβ”€β”€ components
|   |── ProductTile.vue
β”‚   └── ProductGallery.vue
β”œβ”€β”€ plugins
β”‚   └── add-stores.js
β”œβ”€β”€ store
|   |── category.js
β”‚   └── product.js
└── index.js

What we have here is basically a small Nuxt.js application that concentrates only on a specific subdomain of our application. Now when we want to include all these catalog-related features into our app we can do this with a single line of code!

// nuxt.config.js
export default {
  modules: [
    '~/modules/product-catalog/index.js',
  ]
}

Now whenever we will see the issue related to the product catalog in our beloved Jira we will know immediately where to start.

Also, with the modular approach newcomers will know immediately what features are in our app and therefore can understand the business and technical context faster!

.
β”œβ”€β”€ pages
β”‚   └── index.vue
β”œβ”€β”€ modules
|   |── product-catalog
|   |── orders
|   |── payment
|   |── shipping
|   |── inventory
β”‚   └── customers
└── index.js

Summary and whats next

Organizing your code based on the business domain and its subdomains is one of the easiest and most powerful ways to make your app more maintainable. While there are still some challenges left on the table like communication between modules knowing basics is enough to introduce huge benefits into your codebase. Applying patterns from this article does not require advanced programming skills which makes it approachable for developers at all levels of expertise.

In the next part of the series we will see how to build a mechanism similar to Nuxt modules in a β€œvanilla” Vue application!

Learn Vue.js With Vue School

Leave a Reply

Your email address will not be published. Required fields are marked *

Up Next:

Nuxt SSR Optimizing Tips

Nuxt SSR Optimizing Tips