Should we utilize Domain-Driven Design principles and patterns in front-end applications? How far does domain modeling reach from back-end into front-end?
📣 Update: Khalil from the future here. Yes. Yes, Domain-Driven Design principles and patterns belong on the front-end. Come on now, Khalil.
When I wrote this post back in 2019, I was primarily focused on the backend and hadn't spent enough time cutting through and making sense of the client-side space. Since then, I've done a lot of work to identify effective ways to build sustainable front-end applications.
In 2020, I wrote the "Client Side Architecture Basics" guide. This was my initial attempt at understanding the underlying principles at play. In short, what I've realized is that pretty much everything we're doing architecturally on the backend, we could and should be doing on the frontend.
For example, while it may not be popular or easy to separate concerns into layers, it's essential. Why? Because with that separation comes the ability to create an efficient, scalable and testable frontend architecture. With it, if we've figured out how to separate core code from infrastructure code, we can write the equivalent of Acceptance (or spec) Tests on the frontend using extremely fast unit tests that cover a lot of ground and give you tons of confidence.
Within these inner, core code layers (which are synonymous to the domain and application layers from DDD), we have the majority of what makes our app unique. There are no React components here. No mention of things like Hooks or Context. Just pure data models (value objects - domain layer concern), use cases (application layer concern), and presenters (dtos - also application layer) to name a few.
Lastly, if we wish to decouple our client architecture's features from each other, we need to make use of the temporal nature of feature execution. We need to make our architecture event-based. This is how we end up needing Domain Events on the frontend.
So then, does DDD belong on the front-end? Yes, because DDD is just a synthesis of what good software design looks like, the technical patterns and practices we use on the backend should be used on the front-end.
I've left the original post as is for now until I get a chance to update it, but if you have questions about how this could work in practice, reach out to me on Twitter.
Frontend development is an animal of it's own. From jQuery, to Backbone, to EmberJS, to the (terribly painful) journey of dealing with Webpack in it's infancy, to Angular rewrites, React hooks, and all the various module systems we've had to pretend we knew... we've been through a lot.
I think we can all agree on the fact that modern front-end development can be complex.
And it keeps getting even more complex.
Readers of this blog know that I frequently write about Domain-Driven Design (an approach to software development on projects rich with business logic complexity) though I've only exclusively explored it with respect to backend development. Despite that, I've had several people ask me about Domain-Driven Design on the front-end.
Does it make sense to use the same DDD patterns and principles on the frontend that we do on the backend?
The main arguments that I've heard for applying DDD on the frontend are:
The front end space is getting just as complex as the backend space. Apparently, we have Smart UIs now. Modern UIs power complex HTML5 games, AR apps, VR apps, and pretty much anything else you can think about.
State management can be complex. And Domain Events look a lot like how we implement the store architecture to manage application state using Redux or Ngrx.
So... what do you think?
Does DDD belong in the front-end development space?
Generally speaking, no (and I'll explain why in detail), although there are some valid frontend use cases that are remniscient to how we implement DDD on the backend.
Here's what we'll discuss in this article exactly:
Why business logic doesn't belong in the front-end
The real challenge of front-end development
Parallels between DDD concepts and how they apply to the front-end
Good examples of using DDD concepts in the front-end
First argument against it I will make: DDD was meant to address business logic complexity, and business logic doesn't belong on the frontend.
Business logic doesn't belong on the front-end
Okay so why doesn't business logic belong on the front-end?
Anyone?
It's because business logic is the high-level policy that everything else relies on (such as the database, the web front-end, the mobile front-end, etc).
And depending on your application, you might actually require a single source of truth.
Single source of truth
Typically, in a client-server architecture, the backend acts as the single source of truth. The backend is the component that holds all of the high-level policy and dictates who is allowed to do what, when they can do it, and to what degree (how) they can do it.
This works. And we like this because it's easy to understand the current state of the system and how everyone may use it.
For someone to execute a COMMAND or a QUERY against a resource, all they need to do is ask the backend. This is nice because the policy that truly dictates how that interation will go lives in one place (the backend).
Why can't we organize the architecture differently? Why does the backend need to hold the high-level policy?
Well, we can change up that architecture for some use cases. In fact, there are several architectures that do take this approach- P2P, for example. However, these architectures only work for decentralized systems that do not so desparately need a single source of truth in order to govern how the system works.
To avoid going down a rabbit hole on decentralized architectures, the most common systems you'll work on are the ones where there is some sort of centeralized policy.
Todo App (authenticate w/ backend, store data in db)
Social Networking Site (authenticate w/ backend, perform commands and queries against resources you have access to determined by the backend- this is domain complexity)
Even some P2P apps need centralization:
Even in a paid decentralized messaging system, the client needs to check the centeralized server to see if the acccount has sufficient credits.
To protect against change
Frontends are a lot more suceptible to change than backends.
Architecturally, the front-end is a low-level detail.
That means it's more likely that we'll scrap the entire front end for a new one (as my previous company had actually done several times, moving from Backbone to AngularJS to Angular), than scrap the entire backend for a new one.
The factors at play here are stability and volatility.
"Components' dependencies should be in the direction of stability"
To visualize that, if we were to look at the clean architecture that he talks about, we'd notice that it's the domain layer that holds the highest level of stability AND policy.
Clean architecture showing that stability goes towards the domain layer
Why is that? Why does the domain layer hold the highest level of policy and stability?
That's because the domain layer contains the domain modeling code that most closely describes how your business actually works in the real world.
Since it's very unlikely that your business will drastically change, that means that the code that best describes your business is also unlikely to need change.
That's what makes the domain layer quite stable.
And stable components are components that we can rely on. Therefore, it makes sense to organize the unstable (volatile) components to depend on the stable ones, but never make a stable component rely on something unstable. Like a back-end relying on a front-end.
An examplary blunder:Here's one. It's titled, "Exploiting Tinder to get paid features for free". The "TL;DR? Too much on client side" - via Reddit.
Opinion: I think the reason why new developers usually end up working in front-end development jobs straight out of school is because technical managers are aware that the front-end is volatile, and if it were mucked up by a new hire, the business would still be OK. There's a lot more at risk for backend development because it's so critical.
Look at how we normally organize the components of a generic web app.
Notice that the application layer and the domain layer are in the middle of this architecture?
And if we looked at it as a graph, it would form a Directed Acyclic Graph - DAG where the high-level components are on the top, and the low-level ones are on the bottom.
The Open-Closed Principle
In fact, when we do this, we're satisfying the Open-Closed Principlearchitecturally.
OCP says that:
Components should be open for extension, but closed for modification
If your boss told you to change the color of the background on the client app, is that going to break the backend?
No, because architecturally, our system is open for extension (via the front-end, we can change the UI we build on top of it) but closed for modification.
This is what we're doing when we put all the high-level policy in the backend and ensure that the front-end contains no high-level policy.
I think I've said enough on that for now. That's why the front-end shouldn't have business logic.
Categories of business logic sorted by policy
+
These are the categories of business logic ordered from lowest level policy to highest level policy.
6. Presentation logic: Logic that's concerned with how we present something to the user.
5. Data access / adapter logic: Logic concerned with access an infrastructure laywer concern like caches, databases, front-ends, etc.
4. Application layer logic / use case: Application specific logic. In an enterprise with several different applications (think Google's Docs, Sheets, Maps, etc), each application has it's own set of use cases and policy that governs those use cases.
3. Domain layer logic: Core business that doesn't quite fit within the confines of a single entity.
2. Validation logic: Logic that ensures that objects are valid.
1. Core business logic: Logic that can be confined to a single entity. Ex: in a blog, the fact that a `comment` entity is created with `approved: true` or `approved: false` should be central the creation of a `comment` domain object.
Examples of change rippling into other components
+
Pricing model change: Assume we have some Software as a Service application and we want to change the pricing model. If we change the pricing model, we're changing an essential piece of domain layer logic. That has potential to affect everything else like the ui (in order to show new options and perhaps restrict pages) and the database (if new tiers were added, we might need to persist those somehow).
Max users in an account: Let's say there exists an application with Users and Accounts. An account has several users. Assume that this was un-capped for a long time. Accounts could have as many users as they wanted. Suddenly, we decide to add a business rule only allowing 3 users to an account. What's affected? Think about it and share in the comments.
The real challenge of front-end development is architecture
If we're on the same page about keeping domain logic divorced from the front-end, let's direct our attention to the REAL complexity we're facing when building user interfaces.
Temporarily ignoring complex things like rendering svgs, projecting 3D shapes, or facial detection, the most common complexity faced in large front-end applications is the architecture.
Specifically, it's the front-end stack that we use.
The goal of every front-end framework is to simplify the way that we:
Define data (data storage)
Signal that data changes (change detection)
React to data changes (data flow)
There's no shortage of approaches to handle this.
Data storage: Redux/Ngrx store architecture, service-oriented architecture, etc
React to data changes: Observables, hooks, one-way data flow, etc
These early architectural decisions have a profound impact on the quality and ease of development for the remainder of the project's lifespan.
"In my opinion, the most challenging part of front-end development is choosing the stack and ensuring that everyone writes code that adheres to the architectural contours of the decided stack." - https://t.co/dHWnW3dsu3#react#angular#VueJS
I think it would be correct to say that this is as far as high-level policy goes on the front-end. The stack we choose. And the code we write within the framework of choice are the low-level details.
When you choose a frontend architecture, that high-level decision influences the way you write (low-level) code for the rest of the project.
Choosing that stack, organizing code, and consistently ensuring that code is getting written within the architectural choices is (arguably) the most challenging part of front-end development (some might say it's actually CSS 🙂).
None of this has to do with encapsulating a domain model to simplify business logic complexity.
And again, that's what DDD is primarily meant to do.
Drawing parallels to DDD
Let's try to look at the front-end through DDD lenses.
The first thing we try to do in DDD is understand the domain.
Let's use White Label, the vinyl-trading application, for example.
The subdomain of the front-end
White Label is an app where traders can sign up, list the vinyl that they own within their collection, make trades and accept offers.
Add new vinyl - Fill in album details.
For an application like this, you can only assume that there's going to be a LOT of business logic complexity.
Setting up trades, accepting offers, updating offers, handling inventory, not to mention shipping and tracking. There's a lot going on.
Subdomains that probably exist are Trading, Users, Shipping, Billing, and more.
Though, none of that concerns the front-end.
Choices that we make on the front-end should be mutually exclusive from our domain model.
The majority of our front-end code is dumb to the actual problem domain. It largely entails simply validating forms before making API calls, presenting data, and responding to events like clicks or button presses.
The primary concepts of coding within the DOM.
While that's the common way to look at how we interact with front-end concerns on a daily basis, it's not always like that.
Sometimes, in presentation-heavy applications like games or applications with canvas / D3 rendering, domain logic does get duplicated into the presentation layer in order to influence how things are presented.
I'll dive deeper into that in "Good examples of using DDD concepts in the front-end", so keep reading.
Screaming architecture / package by module and subdomain
With respect to the way that I organize code on the front-end, I'll still implement what's called packaging by module, which means that we organize code based on the subdomains.
If these are the level subdomains: Trading, Users, Shipping, Billing.
Then, in a React-Redux project, my folder structure for the Trading subdomain might look like:
src
modules
...
trading # Trading module
components/ # All components for trading subdomain
models/ # All models in trading subdomain
pages/ # All pages in trading subdomain
redux/ # All the redux for the trading subdomain
services/ # All services that interact with trading API
styles/ # All styles for trading components
index.ts
...
Value Objects and validation logic
In Domain-Driven Design, value objects are responsible for validation logic.
If we had a User class and we wanted to ensure that no User could ever be created with an invalid email, we'd change the type of email:string to email: UserEmail and create a UserEmail value object to control creation of the valid UserEmail.
See the example of a UserEmail class below.
import{ TextUtil }from'../utils'import{ Result, Guard }from'../../core'interfaceUserEmailProps{
email:string;}exportclassUserEmailextendsValueObject<UserEmailProps>{// Private constructor. No one can say "new UserEmail('diddle')"privateconstructor(props: UserEmailProps){super(props);}// Factory method, can do UserEmail.create() publicstaticcreate(props: UserEmailProps): Result<UserEmail>{if(Guard.againstNullOrUndefined(props.email)||!TextUtil.isValidEmail(props.email)){return Result.fail<UserEmail>("Email not provided or not valid.");}else{return Result.ok<UserEmail>(newUserEmail(props));}}}
Sharing code between the front-end and the back-end
That begs the question, since validation logic is one of the forms of logic does have a place on the front-end, should we package this value object class and share it on both the front-end and back-end?
Shared kernel: In DDD lingo, a shared kernel is a library or package of domain objects shared across the entire enterprise. This could be an npm library.
A component should have one reason to change. And if that component is relied on by both the frontend and the backend, that means it has two reasons to change.
If a change for a low-level component like the frontend can ripple into something that breaks a high-level component like the backend, then we're also violating OCP.
If you want to go this route, I'd advise having a client-side library that's completely separate from your backend models.
DTOs are your client side models
You probably already knew this, but your dtos that you pass from the backend are pretty much your client-side models.
If your DTOs are just TypeScript interfaces, you can copy those to the front-end or distribute them within your client-side library.
user/services/userService.ts
interfaceUser{
userId:string;
email:string;
firstName:string;
lastName:string;}interfaceIUserService{getUserByUserId(userId:string):Promise<User>;}classUserServiceextendsBaseAPIimplementsIUserService{...asyncgetUserByUserId(userId:string):Promise<User>{// Retrieve the user from the APIconst response =awaitthis.http.get(`user/${userId}`);// Type the responsereturn response.data as User;}}
Client-side user service
Client-side classes or interfaces?: Depending on if you need to add behavioural capabilities to a client-side model, you may need to also request additional metadata in order to hydrate the dto into a client-side class with instance methods instead of just deserialized json object. See the Call Flow example in the final section.
Domain Events are served via websockets or push notifications
Domain events get created and emitted when something interesting happens from within one subdomain.
Other subdomains can subscribe to interesting domain events in order to chain complex domain logic.
In the front-end space, this makes a lot of sense to implement with websockets.
If a front-end app sat there listening in on a websocket connection to the backend with a switch statement hooked up to the client side, the client could present interesting things to the user in the UI as they happen.
But the client-side is never responsible for directly creating domain events.
That's something I've seen recently; where developers confuse a COMMAND from CQRS with a Domain Event.
Good examples of using DDD concepts in the front-end
There are some really good use cases where it makes sense to lean on some of the DDD concepts, in a front-end context.
Here are a few tactics that I've applied in a previous role.
Hydrating domain models
1st person shooter: Consider you've built an online game like Counter Strike: GO, and you want to be able to release new gun skins, gun models, gun sounds, and what not. Your goal is to be able to dynamically create new guns with their color, render, and behavior (recoil, burst rate) without having to deploy a patch everytime a new gun comes out. Or perhaps players themselves could create their own frankenstein-y guns.
Since the front-end is responsible for the presentation of those guns, a dto could return not only the basic details about the gun, but also all of the additional metadata required in order to hydrate an instance of a gun class, so that the instance methods and properties dynamically reflect it's presentation and behaviour within the game.
Call flow visualizations: Earlier this year I worked on Talkify, a visual call flow tool built with Angular and D3, enabling small business owners to drag and drop Call Flow Nodes onto a canvas in order to create these really complex call flows. Each Call Flow Node was different in the sense that they all had their own presentation, behavior, chaining rules (am I allowed to put something after this, or is this a terminal node), and editable form.
For example, the number node acted differently from the busines hours node and the AWS lambda node (yeah, it was really cool- you could hook up lambda functions to a call flow).
Again, there was a sort of dynamicism required because the backend team would deploy new Call Flow Nodes all the time, and the front-end needed only require the metadata for each node via API call in order determine how to render it and how to hook up the specific presentation logic for each node.
Real-time domain events
Location tracking: Also earlier this year, I was doing work on a mobile app for a company that builds marine magnetometers. Using a steady stream of domain events from a backend application, the mobile app would render a map of your location, your boat's position on the map, and the direction of the magnetometer at the back of the boat.
Git and CI Tools: Tools like GitHub, Gitlab, and Netlify do this all the time. When someone merges a PR, or a build fails, there's usually a UI change that happens if you're looking at the same page.
Takeaway
Business logic doesn't belong on the front-end because of single-source of truth constraints in centralized applications, the front-end is volatile and prone to change, and components should always point towards the direction of stability.
The real complexity of front-end development is maintaining a consistent architecture based on the decisions of the tech stack, not encapsulating a domain model.
Be careful to not violate OCP when sharing code between the front-end and the back-end. Create a client-side library.
Applications with heavy amounts of presentation logic are the ones that would most benefit from using a client-side presentation model from hydrated domain objects (DTOs).
Commenting has been disabled for now. To ask questions and discuss this post, join the community.
Roman
4 years ago
I think your RSS is exposing drafts. Maybe something to look into.
Khalil Stemmler
4 years ago
Ay, that's the one. Respect, brother.
Jason Roos
4 years ago
I would suggest a couple other use cases for front-end DDD:
1) when the back end models are difficult to adapt to the needs of the front end, as often occurs with massive corporations involving equally massive amounts of bureaucratic red tape
2) when there is no back end, i.e., it's just a web app with static resources hosted on a CDN, as often occurs when either money or skilled human resources are lacking or when performance, and thus the need to limit or eliminate network round-trips, is of utmost concern.
Khalil Stemmler
4 years ago
1) Haha. Yeah, that's not a fun place to be in. But in that case, if the backend models were hard to adapt for a new front-end application, best thing to do is create a new subdomain (we did this at a big telecom company that wanted to create a new app for a completely different market but re-use existing infrastructure) and then instead of adapting to the needs of the front end, the new bounded context involved upstream integrations to existing subdomains like Users, Notifications, Billing, etc.
The architectural benefit was that all of the reasons for the new app to change were limited to that new subdomain :)
2) For sure! Although DDD most of the tactical DDD looks like repos, entities, value objects, use cases, might be overkill :p it would be cool to see how to apply DDD to something like a music player hooked up to cloudfare with no real backend.
pjotr
4 years ago
I have a question regarding your Trading subdomain folder structure. what is the pages folder for? do you not include jsx in React components (you have a components folder so I assume this is where you place React components) ?
BTW thanks for responses in the previous posts. the only tricky thing is I had to remember where I posted them, because there's no such thing as notifications or another signal that you responded.
Sam Hatoum
4 years ago
Awesome article as always, thank you for writing.
I have been working with my team on doing DDD in the UI, with a particular focus on the concept of an "Interaction Domain" so I thought I'd share some of our approach.
We look at an UI component and decouple the interaction model out of it. This is so the UI component does not have to manage state (directly or indirectly) and instead calls meaningful methods like "submitForReview" or "revertChanges" on an aggregate. Just like in DDD, the aggregate allows us to hide internal state and create a transactional boundary.
Just like a "business" domain, the Interaction domain also requires stability and policy. A good litmus test I like to use for the Interaction domain is: "could we use a different presentation technology with the abstraction we created such as Vue/Angular/etc?"
We can also build complex use-cases around multiple interaction aggregates - using say a viewService - then we can pass the viewService into UI components.
We're only just starting this approach so we have much to learn. But it's great to see that you (and others?) are also thinking about it.
Sam :)
Khalil Stemmler
4 years ago
I really love the idea of decomposing all of the interactions into its own layer.
You and I might be doing something similar, although yours sounds a lot better insulated from library concerns.
What I mean is this- I'm using Redux for DDDForum.com and I call all of the interactions operators. Here's an example of all the operators in the forum subdomain.
But there's a dependency to Redux because it uses `dispatch`. In order to go your direction, I'd need to define an adapter layer to invert needing to rely on redux constructs directly.
Thanks for sharing, Sam. Good luck and I'd love to chat in more detail about how you're doing that sometime.
Henrik Valerian
4 years ago
As always.. it depends on the use case and requirements of the application you’re building. Also, shared models and logic does not have to violate SDP and SRP. Frontend domain entities and business logic can be generated for the frontend, by the backend, at build time. Thereby only having one source of truth. Changes to the model in the backend will then break the build of the frontend, until frontend has been modifed accordingly. Further more, if those domain entities are reflected in your DTOs, then your APIs are as stable and long lived as your domain as well. Letting you spend your time mostly building UIs.
Khalil Stemmler
4 years ago
I have a fairly strong opinion against "generating business logic". I believe that should never be a thing. Those are the family jewels, yo.
Also, would it just be cloned business logic? As in, would it literally just be copying files? I don't trust anything automating the important parts of my app.
The furthest I would ever take something like that is using Swagger to generate client-side RESTful APIs for my front-end to use.
To me, slim, logic-less backend DTOs are my client-side models. That's it. If you want to script those to be copied to the front-end, I like that.
But I'm against copying business logic from one to the other because they both change for different reasons.
nickwalt
4 years ago
If the UI serves the domain there will be aspects of its design that are driven by the domain. I really don't see any separation or reason to exclude the frontend from DDD processes.
Type driven design is a key aspect of DDD where primitives are abstracted to correctly model domain entities in the data. Ideally, these domain entities should flow from persistent storage to UI and back without loosing integrity or meaning.
The UI must indeed be modelled from the domain. This includes task and behaviour driven design.
Otherwise the implementation of the model in the UI can become anaemic.
Entities (typed data), naming, semantics, behaviour, validation, translation: these all come from the domain and they exist in the frontend no less than the backend.
Is it possible that what you are saying is that we cannot prescribe backend DDD processes on to the frontend?
Check out Scott Wlaschin:
https://youtu.be/Up7LcbGZFuo
He touches on frontend in that talk.
Khalil Stemmler
4 years ago
"If the UI serves the domain there will be aspects of its design that are driven by the domain."
Agreed. If the UI serves the domain. The vast majority of consumer-facing apps that primarily involve putting data forms, rendering views, and interacting with a REST or GraphQL API, don't need to involve the front-end in the encapsulation of the core domain rules.
This is further proven by the ability to create 10 different versions of a front-end using the same backend (we've done this for clients using the latest JS frameworks), while the opposite is always more expensive and not always possible.
There are exceptions, like some of the ones we talked about in the article (ones where presentation logic IS part of the domain, like multiplayer games, graphics, and coordinate based rendering systems), but my pragmatic opinion is that it's overkill (YAGNI) to duplicate business rules on both sides of the stack if it's not necessary, because the front-end is volatile.
"The UI must indeed be modelled from the domain. This includes task and behaviour driven design."
BDD is pretty cool, but I wouldn't say button clicks and file uploads are something within my core domain- nor would I care to model. There are ways to do this nicely, like stuff Sam Hatoum is working on, but you have to decide if it's a worthwhile investment. I can't say if I don't know your domain.
"Is it possible that what you are saying is that we cannot prescribe backend DDD processes on to the frontend?"
Absolutely not. You could. But unless presentation was a part of the core domain, I wouldn't bother. For anything else, the front-end models would be pretty thin.
"Check out Scott Wlaschin. He touches on frontend in that talk."
I've seen this talk several times now. Love it. He briefly mentions front-end, but to represent upper-bound constraints, like Name can't have more than 50 characters. I don't think you need a domain model for that.
Yazan Alaboudi
3 years ago
I have been experimenting with a DDD architecture on the front end and have landed here. Very very nice article. Thank you for sharing. I very much like your point about how the frontend is a low level policy in comparison to the backend. I am also aligned with your opinion that games and other offline heavy applications can be modelled using DDD.
With that said, I'm a little conflicted. With the rise of single page applications, there is a higher emphasis on client side state management. With this added offline responsibilities, front end applications are starting to further resemble games in a sense. The frontend, in the end of the day, is trying to fulfill a role that the backend can't: Providing a user with a good visual and interactive UX experience. Similar to other services in the system, the frontend will sometimes be reliant on other services to fulfill some needs while also using its own locale state management to provide value.
I think the more interactive and reliant an application is locale state to drive value, the more of a candidate the application can become to embrace DDD.
jmichelson@unitec-corp.com
3 years ago
Front-end DDD is a good idea when used wisely. Which is to say that I add additional DDD boilerplate where complex business rules mandate it. Same with state. I use Breeze.js for 90% of my state/persistence management since 90% of persistence operations are simple CRUD and the overhead of ngrx is just unnecessary busy work.
Alireza
3 years ago
Nice read.
There is a one thing to share
regarding to this sentence "business logic doesn't belong on the frontend", I was involved in writing a complex form entry system which basically needs the knowledge of domain layer business logic in order to update the form and showing correct state, because that system checked different business rules to validate and update data base on user's input. majority of business logic was written in frontend and backend was just a system to coordinate the data and write it in the database, maybe there were some cases that you could see some business rules are applying in the backend but again business rules leaked to the frontend because of the nature of the application design which was based on SPA. it was very hard to put all those logic in backend cause in this case frontend had to bother back-end every time something happens.
I think as you mentioned the front-end architecture style and stack would have an impact on this situation.
even with emerging the SPA applications I've noticed more people tend to put more of their logic into frontend layer and majority of the front-end code is not dumb to the actual problem domain anymore.
Juanito
2 years ago
This was a great analysis and I really liked it! However, I have implemented DDD principles in front-end. My discrepancies begin when you state the following:
The goal of every front-end framework is to simplify the way that we:
Define data (data storage)
Signal that data changes (change detection)
React to data changes (data flow)
This is, in my opinion, way too similar to issues that DDD, and clean architecture are directly addressing. Back-end has to solve the same issues. The fact that frameworks such as Redux and Rxjs exist prove how valuable it is to (a) define modules and boundaries, (b) model data from behavior and not the other way around, (c) separate flow of dependencies from flow of control, and (d) data flow modelling, usually reactive in front-end.
When you pick Redux on its own, for example, the store becomes your domain object, effects are your repositories, actions and selectors are your adapters, your data flow model is flow-based (search for FBP), and Redux becomes your stable and abstract dependency layer, while your UI components are your unstable and concrete layer. Your store is harder to change as your UI layer grows.
From the clean architecture principles, your frameworks should be details external to your core, to your domain. They should be hidden inside our interface. And for me, yes, Redux should be hidden as an implementation detail. I usually hide it behind façades for actions, and behind custom hooks for selectors. This flexibility has allowed me to:
Insert a complete front-end logic that does not need Redux and is simpler in its implementation, but that has the same interface.
Add other frameworks where they make more sense, such as third-party libraries and GraphQL.
Wrap complex logic that pertains to front-end only (such as the examples you mentioned in the article) without hacking the architecture.
Delay the decision of which tech stack to use until we have a more robust application, without having to rewrite everything or make huge refactors.
In this architecture, Redux usually becomes a repository implementation, and the validation layer becomes part of the front-end's business domain, rather than reducers.
Having said all that, I'm also against a single business domain for both back-end and front-end, or replicating the exact domain structure from back-end into front-end. In my experience, the core front-end domain model resembles much more the presenter back-end model (yep, DTOs) than the core back-end model, and mostly ignores the business domain architecture.
Sorry for the huge comment. I may need a blog post to completely explain all this...
On frontend models
2 years ago
Hi
I have some experience about directly hydrating frontend models from DTOs
First, from maintainability reasons, I believe DTO should never ever be directly used on frontend, but I think you mention that. What I mean is something like (on the frontend): `const comment = Comment.createFromDto(apiResult.comment)`
What it does is protecting frontend layer from changes in API. Its very easy to rename field or something like that, because its a single place that needs to be updated (model factory or some other mapping layer).
So now we have 2 representations on frontend - DTO which is data source and the model itself. We can (should) avoid any business logic here, its not the scope. But we do have a different scope - how model can be represented on frontend.
For example, comment can be visible under the post and can have a "expanded: boolean" state. Its not business state, but we are doing frontend here, where this state is actually significant and we work around it.
I dont use "expanded" flag in my `Comment` model, I create some "View Model" for it, which will be a different for of comment, representing the UI job. My Comment "UI" might require more information (e.g. author name and URL to the profile, taken from Author model, but raw `Comment` only contains `authorID`).
For this scenario I write my "queries" - based on comment reference I construct "CommentView" which actually have state (expanded).
This is usually my components props (`props: {comment: CommentView})`. Its a least knowledge principle, but I also wrap it with class to ensure validity. I dont want to pass entire author AND comment to couple it with framework code.
Also, I can easily test stuff on their own, without any complex React rendering.
Hi, I've mixed feelings about your post. But in any case, a good one. This is a large post and is really difficult to point out, all the areas where I disagree (which means we agree on a lot). But the sensation I perceive is that you have a lot of experience on the backend and DDD, but not in the frontend (From a modern perspective). I will try to justify my assumptions:
I like to say, the FrontEnd is the new BackEnd.
You said:
The majority of our front-end code is dumb to the actual problem domain.Â
That's a very bold sentence. Which I disagree with, you could develop offline applications which require that domain logic is implemented. (There is a lot more example, but for keeping short the debate, I think that is a good one). This will ensure you that your backend only verifies things when another 3rd Party integrates because the front does it well. This means that action on front end can be synced smoothly with the backend like the flow of the app is only one. You probably are applying the same logic twice, but if one fails, the response is immediately and offline. You probably argue some of this with another sentence you said about sharing code between the front-end and the back-end:
You could do that, but sharing domain-layer code between the front-end and the back-end is an architecturally messy decision that violates the Stable Dependency Principle (SDP) and the Single Responsibility Principle. A component should have one reason to change. And if that component is relied on by both the frontend and the backend, that means it has two reasons to change.
Which from my point of view is completely false. Obviously, you can find a bad solution and have that problem. But when you share domain code between front and back if a component changes it won't affect that shared domain, because it is separated from a component. It will only change the adapter on the front end that uses the shared domain. If not, then you are not doing DDD, you are doing something else.
In any case, thanks for sharing, that's a great article, touches a lot of points. I propose to change it to a series because some points have a lot to discuss. And can be hard for the reader or for you to read and answer all the questions and interpretations.
Stay in touch!
Enjoying so far? Join 15000+ Software Essentialists getting my posts delivered straight to your inbox each week. I won't spam ya. đź––
Get notified
About the author
Khalil Stemmler, Software Essentialist ⚡
I'm Khalil. I turn code-first developers into confident crafters without having to buy, read & digest hundreds of complex programming books. Using Software Essentialism, my philosophy of software design, I coach developers through boredom, impostor syndrome, and a lack of direction to master software design and architecture. Mastery though, is not the end goal. It is merely a step towards your Inward Pull.
12 Comments
Commenting has been disabled for now. To ask questions and discuss this post, join the community.
I think your RSS is exposing drafts. Maybe something to look into.
Ay, that's the one. Respect, brother.
I would suggest a couple other use cases for front-end DDD:
1) when the back end models are difficult to adapt to the needs of the front end, as often occurs with massive corporations involving equally massive amounts of bureaucratic red tape
2) when there is no back end, i.e., it's just a web app with static resources hosted on a CDN, as often occurs when either money or skilled human resources are lacking or when performance, and thus the need to limit or eliminate network round-trips, is of utmost concern.
1) Haha. Yeah, that's not a fun place to be in. But in that case, if the backend models were hard to adapt for a new front-end application, best thing to do is create a new subdomain (we did this at a big telecom company that wanted to create a new app for a completely different market but re-use existing infrastructure) and then instead of adapting to the needs of the front end, the new bounded context involved upstream integrations to existing subdomains like Users, Notifications, Billing, etc.
The architectural benefit was that all of the reasons for the new app to change were limited to that new subdomain :)
2) For sure! Although DDD most of the tactical DDD looks like repos, entities, value objects, use cases, might be overkill :p it would be cool to see how to apply DDD to something like a music player hooked up to cloudfare with no real backend.
I have a question regarding your Trading subdomain folder structure. what is the pages folder for? do you not include jsx in React components (you have a components folder so I assume this is where you place React components) ?
BTW thanks for responses in the previous posts. the only tricky thing is I had to remember where I posted them, because there's no such thing as notifications or another signal that you responded.
Awesome article as always, thank you for writing.
I have been working with my team on doing DDD in the UI, with a particular focus on the concept of an "Interaction Domain" so I thought I'd share some of our approach.
We look at an UI component and decouple the interaction model out of it. This is so the UI component does not have to manage state (directly or indirectly) and instead calls meaningful methods like "submitForReview" or "revertChanges" on an aggregate. Just like in DDD, the aggregate allows us to hide internal state and create a transactional boundary.
Just like a "business" domain, the Interaction domain also requires stability and policy. A good litmus test I like to use for the Interaction domain is: "could we use a different presentation technology with the abstraction we created such as Vue/Angular/etc?"
We can also build complex use-cases around multiple interaction aggregates - using say a viewService - then we can pass the viewService into UI components.
We're only just starting this approach so we have much to learn. But it's great to see that you (and others?) are also thinking about it.
Sam :)
I really love the idea of decomposing all of the interactions into its own layer.
You and I might be doing something similar, although yours sounds a lot better insulated from library concerns.
What I mean is this- I'm using Redux for DDDForum.com and I call all of the interactions operators. Here's an example of all the operators in the forum subdomain.
But there's a dependency to Redux because it uses `dispatch`. In order to go your direction, I'd need to define an adapter layer to invert needing to rely on redux constructs directly.
Thanks for sharing, Sam. Good luck and I'd love to chat in more detail about how you're doing that sometime.
As always.. it depends on the use case and requirements of the application you’re building. Also, shared models and logic does not have to violate SDP and SRP. Frontend domain entities and business logic can be generated for the frontend, by the backend, at build time. Thereby only having one source of truth. Changes to the model in the backend will then break the build of the frontend, until frontend has been modifed accordingly. Further more, if those domain entities are reflected in your DTOs, then your APIs are as stable and long lived as your domain as well. Letting you spend your time mostly building UIs.
I have a fairly strong opinion against "generating business logic". I believe that should never be a thing. Those are the family jewels, yo.
Also, would it just be cloned business logic? As in, would it literally just be copying files? I don't trust anything automating the important parts of my app.
The furthest I would ever take something like that is using Swagger to generate client-side RESTful APIs for my front-end to use.
To me, slim, logic-less backend DTOs are my client-side models. That's it. If you want to script those to be copied to the front-end, I like that.
But I'm against copying business logic from one to the other because they both change for different reasons.
If the UI serves the domain there will be aspects of its design that are driven by the domain. I really don't see any separation or reason to exclude the frontend from DDD processes.
Type driven design is a key aspect of DDD where primitives are abstracted to correctly model domain entities in the data. Ideally, these domain entities should flow from persistent storage to UI and back without loosing integrity or meaning.
The UI must indeed be modelled from the domain. This includes task and behaviour driven design.
Otherwise the implementation of the model in the UI can become anaemic.
Entities (typed data), naming, semantics, behaviour, validation, translation: these all come from the domain and they exist in the frontend no less than the backend.
Is it possible that what you are saying is that we cannot prescribe backend DDD processes on to the frontend?
Check out Scott Wlaschin:
https://youtu.be/Up7LcbGZFuo
He touches on frontend in that talk.
"If the UI serves the domain there will be aspects of its design that are driven by the domain."
Agreed. If the UI serves the domain. The vast majority of consumer-facing apps that primarily involve putting data forms, rendering views, and interacting with a REST or GraphQL API, don't need to involve the front-end in the encapsulation of the core domain rules.
This is further proven by the ability to create 10 different versions of a front-end using the same backend (we've done this for clients using the latest JS frameworks), while the opposite is always more expensive and not always possible.
There are exceptions, like some of the ones we talked about in the article (ones where presentation logic IS part of the domain, like multiplayer games, graphics, and coordinate based rendering systems), but my pragmatic opinion is that it's overkill (YAGNI) to duplicate business rules on both sides of the stack if it's not necessary, because the front-end is volatile.
"The UI must indeed be modelled from the domain. This includes task and behaviour driven design."
BDD is pretty cool, but I wouldn't say button clicks and file uploads are something within my core domain- nor would I care to model. There are ways to do this nicely, like stuff Sam Hatoum is working on, but you have to decide if it's a worthwhile investment. I can't say if I don't know your domain.
"Is it possible that what you are saying is that we cannot prescribe backend DDD processes on to the frontend?"
Absolutely not. You could. But unless presentation was a part of the core domain, I wouldn't bother. For anything else, the front-end models would be pretty thin.
"Check out Scott Wlaschin. He touches on frontend in that talk."
I've seen this talk several times now. Love it. He briefly mentions front-end, but to represent upper-bound constraints, like Name can't have more than 50 characters. I don't think you need a domain model for that.
I have been experimenting with a DDD architecture on the front end and have landed here. Very very nice article. Thank you for sharing. I very much like your point about how the frontend is a low level policy in comparison to the backend. I am also aligned with your opinion that games and other offline heavy applications can be modelled using DDD.
With that said, I'm a little conflicted. With the rise of single page applications, there is a higher emphasis on client side state management. With this added offline responsibilities, front end applications are starting to further resemble games in a sense. The frontend, in the end of the day, is trying to fulfill a role that the backend can't: Providing a user with a good visual and interactive UX experience. Similar to other services in the system, the frontend will sometimes be reliant on other services to fulfill some needs while also using its own locale state management to provide value.
I think the more interactive and reliant an application is locale state to drive value, the more of a candidate the application can become to embrace DDD.
Front-end DDD is a good idea when used wisely. Which is to say that I add additional DDD boilerplate where complex business rules mandate it. Same with state. I use Breeze.js for 90% of my state/persistence management since 90% of persistence operations are simple CRUD and the overhead of ngrx is just unnecessary busy work.
Nice read.
There is a one thing to share
regarding to this sentence "business logic doesn't belong on the frontend", I was involved in writing a complex form entry system which basically needs the knowledge of domain layer business logic in order to update the form and showing correct state, because that system checked different business rules to validate and update data base on user's input. majority of business logic was written in frontend and backend was just a system to coordinate the data and write it in the database, maybe there were some cases that you could see some business rules are applying in the backend but again business rules leaked to the frontend because of the nature of the application design which was based on SPA. it was very hard to put all those logic in backend cause in this case frontend had to bother back-end every time something happens.
I think as you mentioned the front-end architecture style and stack would have an impact on this situation.
even with emerging the SPA applications I've noticed more people tend to put more of their logic into frontend layer and majority of the front-end code is not dumb to the actual problem domain anymore.
This was a great analysis and I really liked it! However, I have implemented DDD principles in front-end. My discrepancies begin when you state the following:
The goal of every front-end framework is to simplify the way that we:
This is, in my opinion, way too similar to issues that DDD, and clean architecture are directly addressing. Back-end has to solve the same issues. The fact that frameworks such as Redux and Rxjs exist prove how valuable it is to (a) define modules and boundaries, (b) model data from behavior and not the other way around, (c) separate flow of dependencies from flow of control, and (d) data flow modelling, usually reactive in front-end.
When you pick Redux on its own, for example, the store becomes your domain object, effects are your repositories, actions and selectors are your adapters, your data flow model is flow-based (search for FBP), and Redux becomes your stable and abstract dependency layer, while your UI components are your unstable and concrete layer. Your store is harder to change as your UI layer grows.
From the clean architecture principles, your frameworks should be details external to your core, to your domain. They should be hidden inside our interface. And for me, yes, Redux should be hidden as an implementation detail. I usually hide it behind façades for actions, and behind custom hooks for selectors. This flexibility has allowed me to:
In this architecture, Redux usually becomes a repository implementation, and the validation layer becomes part of the front-end's business domain, rather than reducers.
Having said all that, I'm also against a single business domain for both back-end and front-end, or replicating the exact domain structure from back-end into front-end. In my experience, the core front-end domain model resembles much more the presenter back-end model (yep, DTOs) than the core back-end model, and mostly ignores the business domain architecture.
Sorry for the huge comment. I may need a blog post to completely explain all this...
Hi
I have some experience about directly hydrating frontend models from DTOs
First, from maintainability reasons, I believe DTO should never ever be directly used on frontend, but I think you mention that. What I mean is something like (on the frontend): `const comment = Comment.createFromDto(apiResult.comment)`
What it does is protecting frontend layer from changes in API. Its very easy to rename field or something like that, because its a single place that needs to be updated (model factory or some other mapping layer).
So now we have 2 representations on frontend - DTO which is data source and the model itself. We can (should) avoid any business logic here, its not the scope. But we do have a different scope - how model can be represented on frontend.
For example, comment can be visible under the post and can have a "expanded: boolean" state. Its not business state, but we are doing frontend here, where this state is actually significant and we work around it.
I dont use "expanded" flag in my `Comment` model, I create some "View Model" for it, which will be a different for of comment, representing the UI job. My Comment "UI" might require more information (e.g. author name and URL to the profile, taken from Author model, but raw `Comment` only contains `authorID`).
For this scenario I write my "queries" - based on comment reference I construct "CommentView" which actually have state (expanded).
This is usually my components props (`props: {comment: CommentView})`. Its a least knowledge principle, but I also wrap it with class to ensure validity. I dont want to pass entire author AND comment to couple it with framework code.
Also, I can easily test stuff on their own, without any complex React rendering.
When I work on state, I do something like this:
Hi, I've mixed feelings about your post. But in any case, a good one. This is a large post and is really difficult to point out, all the areas where I disagree (which means we agree on a lot). But the sensation I perceive is that you have a lot of experience on the backend and DDD, but not in the frontend (From a modern perspective). I will try to justify my assumptions:
I like to say, the FrontEnd is the new BackEnd.
You said:
The majority of our front-end code is dumb to the actual problem domain.Â
That's a very bold sentence. Which I disagree with, you could develop offline applications which require that domain logic is implemented. (There is a lot more example, but for keeping short the debate, I think that is a good one). This will ensure you that your backend only verifies things when another 3rd Party integrates because the front does it well. This means that action on front end can be synced smoothly with the backend like the flow of the app is only one. You probably are applying the same logic twice, but if one fails, the response is immediately and offline. You probably argue some of this with another sentence you said about sharing code between the front-end and the back-end:
You could do that, but sharing domain-layer code between the front-end and the back-end is an architecturally messy decision that violates the Stable Dependency Principle (SDP) and the Single Responsibility Principle. A component should have one reason to change. And if that component is relied on by both the frontend and the backend, that means it has two reasons to change.
Which from my point of view is completely false. Obviously, you can find a bad solution and have that problem. But when you share domain code between front and back if a component changes it won't affect that shared domain, because it is separated from a component. It will only change the adapter on the front end that uses the shared domain. If not, then you are not doing DDD, you are doing something else.
In any case, thanks for sharing, that's a great article, touches a lot of points. I propose to change it to a series because some points have a lot to discuss. And can be hard for the reader or for you to read and answer all the questions and interpretations.