A domain-driven Vue.js Architecture

Thomas Holland
Nov 25, 2019 · 13 min read
The view from an inner courtyard, enclosed by buildings, themed in Vue’s brand colors. In the sky, there thrones the Vue logo
The view from an inner courtyard, enclosed by buildings, themed in Vue’s brand colors. In the sky, there thrones the Vue logo
The sky is the limit once you internalize Domain-Driven Design.

The advent of a sustainable software project

For one of our current Vue apps, we set ourselves a couple of goals that should ideally improve our long-term development performance. This story focuses on the architectural side of things. We’ve got subsequent stories about effective team processes, documentation and others in the making, so stay tuned and follow our Bauer + Kirch publication on medium. But first, let’s take a look at some of the goals that still function as our North Star to this day:

  • Maintainability: Some of the code of our legacy projects that run stable in production makes it pretty hard to incorporate changes in a timely manner since everything is so intertwined and obfuscated. For this new app, we wanted to make sure to prevent such cases from the get-go.
  • Short ramp-up phase: Similarly, our decisions should allow for new team members as well as colleagues that are not part of the project’s core development team to get started fairly easily. Team composition, as well as internal product ownership, could change in the future and we’d like to be prepared for that.
  • Stability: A confusing software architecture makes it easier to introduce unintentional changes in parts of the application not obviously tied to the parts that were changed. Of course, there is a lot more to stability than architecture but it certainly is one piece of the puzzle.

The anatomy of Vue.js

Software architecture, as described by Wikipedia,

  • State management: In addition to handling data on component-level, the de-facto standard solution for handling centralized state is Vuex.
  • Network: It’s likely for a web app to communicate with external services, such as REST or GraphQL APIs. You’d probably do that with e.g. fetch or something more sophisticated like Vue Apollo.
  • Routing: Vue Router enables you to navigate between virtual pages inside of your Single Page Application.
  • Internationalization: This is handled by the third-party library vue-i18n.
  • Styling and animation: Vue promotes the usage of scoped CSS inside of Single File Components. We actually handle styling a bit differently but that’s for another blog post. Nonetheless, we consider styling and animation to be one of the typical fundamental elements.

One cannot not have an architecture.

Couldn’t have said it better myself. Okay, enough introductory gibberish.

Let’s talk about folders

A folder structure is not intrinsically coupled to any architecture. However, it is possible to make use of your filesystem to represent parts of it. After all, folders do represent a hierarchical structure; many architectures make use of hierarchies as well.

Vue.js architectures in the wild

Imagine a hypothetical social network app where users can create a profile, upload a profile picture and change some settings around privacy and such. Typical code examples of SPAs that can be found in numerous blog posts etc. often suggest a folder structure that looks something like the one below.

# Folder Structure            # Business Domainsrc/
├─┬─ components/
├─── App.vue # General
├─── Avatar.vue # Profile
├─── Home.vue # Homepage
├─── Profile.vue # Profile
└─── Settings.vue # Settings
├─┬─ images/
├─── gearIcon.svg # Settings
└─── defaultAvatar.png # Profile
├─── router/
├─── router.js # General
└─── routes.js # General
├─┬─ store/
├─── profileModule.js # Profile
├─── settingsModule.js # Settings
├─── store.js # General
└─── userModule.js # General
└─┬─ util/
└─── api.js # General

The Good, the Bad and the Ugly

So, “what’s so bad about a technically-driven architecture” you may ask. As a matter of fact, it is not necessarily bad per se. Especially if your app’s complexity is manageable, going the extra mile and digging deeper into architectural finesse might indeed be overkill. However, as your app’s complexity grows, so does the mental overhead of scrambling together all the scattered pieces of one particular business domain. As you’re assigned a task in the form of a user story, more often than not you will find yourself working in one or only a few business domains. For example:

  • Should the user be able to upload audio files?
  • Where do I put the related business logic? Into a store module? Into components?
  • Speaking of components: I will need some sort of form for the user to select a song. Didn’t we have a form for that profile pic already? Also, I will probably have to build a small media player to pause and resume the song.
  • And so forth …

Treating the folders to a makeover

One thing that helped us reach that overall state of happiness was indeed to remodel our architecture with a domain-driven approach in mind. In terms of folder structure, this is what we essentially came up with, again based on the example of a social network:

# Folder Structure            # Business Domainsrc/
├─┬─ app/
├─┬─ connector/
└─── api.js # General
├─┬─ model/
├─── store.js # General
└─── userModule.js # General
├─┬─ router/
├─── router.js # General
└─── routes.js # General
└─┬─ ui/
└─── App.vue # General
├─┬─ homepage/
└─┬─ ui/
└─── Home.vue # Homepage
├─┬─ profile/
├─┬─ images/
└─── defaultAvatar.png # Profile
├─┬─ model/
└─── profileModule.js # Profile
└─┬─ ui/
├─── Avatar.vue # Profile
└─── Profile.vue # Profile
└─┬─ settings/
├─┬─ images/
└─── gearIcon.svg # Settings
├─┬─ model/
└─── settingsModule.js # Settings
└─┬─ ui/
└─── Settings.vue # Settings

Relations among software elements.

Folders sure are fascinating and all, but in the end, they’re just one part of the equation.

A diagram of the architecture that shows the business domains. Each domain implements the technical layers.
A diagram of the architecture that shows the business domains. Each domain implements the technical layers.
A picture is worth a thousand words.

Establishing a common mental model

Meme: No need to separate if you have no concerns.
Meme: No need to separate if you have no concerns.

Technical Layers

Now that we established a common vocabulary, it becomes a lot easier to talk about developing new features as a team. Each technical layer has got a defined responsibility and when thinking “We need to implement a new form in this story”, everybody is pretty much on the same page in terms of implementation details from the get-go. We effectively removed most of the friction that resulted from bikeshedding around technical details that were not part of the actual problem at hand. On a side note, this is essentially the same realization that sparked the idea of developing the code formatter Prettier, just on another level.

  • UI: This is the home of our components and styles. Components decide what to render mostly on the basis of what the model is telling them about the current state of affairs. Sometimes though, you might only want to expand a list of items or something. In those cases, it’s perfectly fine to handle that logic locally. It’s unlikely that expanding a list affects the actual state of your application in a meaningful way.
  • Connector: As soon as the need arises to talk to external services, we implement a Connector. A Connector implements a set of methods that communicate with e.g. an email service. One could imagine a method emailService.send(from, to, subject, content) that’s called from the model layer of a Functional Slice Contact.

The App Slice

The App Slice takes up a special role. It’s the slice that functions as the main entry point into our application as well as a reservoir for shared components and utils. Speaking in the language of Atomic Design, it’s preferable to have a set of atoms and low-level molecules such as buttons or text inputs to only be present in one mutual slice. That would be the App Slice.

Constraints

On top of the conceptual structure, we assume a couple of constraints:

  • Each Technical Layer may only access the layer directly beneath. It’s for example forbidden to access Connector from UI or the other way around.
  • Flow specific slices may only access themselves and App.
  • This in turn means that App may only access itself.

A word of caution

Dan Abramov, core member of the React team, spent probably at least an order of magnitude more time on all of this than most of us. That should be a good enough reason to listen to his 2 cents on the matter in this thread on twitter:

because we’re so bad at programming, it’s important that we often re-evaluate when splitting a problem in a certain way is helpful, and when there’s a way that works better with problems of a certain shape.

And that’s what we’d like to end with. Don’t consider this particular architecture the holy grail of developing Single Page Applications. Do critically think about your problem first. Then, maybe our findings will inspire you to find your own solution. It might turn out that it in fact does fit your use case perfectly. Just be aware that this is not a one-size-fits-all. It worked pretty well for our team and our specific use case.

Further Reading

  • Check out Domain-Driven Design by Eric Evans.
  • Paul Sweeney makes a compelling case for Micro-Frontends here on Medium. There was a bit of a buzz around them lately and while some people threw some shade and the pattern should certainly be considered carefully, we feel that some of the ideas stem from a similar motivation as the ones illustrated here. It’s not for everyone and it’s a road not much traveled but who knows. It might just fit your use case.
  • The Software Architecture Chronicles gives an in-depth overview and is a recommended read for those interested in software architecture in general.

Conclusion

So, did we meet our initial goals? The biggest one for us as a team was to achieve a mutual understanding of what our codebase and the relations within should look like. In that regard, the architecture we came up with is a whopping success. Everyone is on board and feels at home when working on the project. Implementing new features comes naturally and so far just feels right.

Bauer + Kirch

Smart software applications, internet solutions and mobile…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store