78

I'm reading about software architectures like Hexagonal architecture, Onion architecture etc.

They put a big emphasis on decoupling. The business logic sits at the centre and the UI sits on the outside. The idea is that the UI should not touch the business rules at all. It should be totally dumb and should just relay commands and display any updated output.

The problem I have is that I find this quite difficult to imagine in practice. The UI will likely implement conditional rendering, and much of that conditional rendering is a business rule in itself.

Imagine a shopping cart system. For whatever reason the client decides they do not want promo codes to be added to an empty basket (this maybe isn't a great example but run with me). In your conditional render in the UI you would have to check if items.Count == 0 - and boom, you've just implemented business logic rules in the UI.

Or would you have this rule in your DTO with a property called CanUserInputPromoCode? Even then, the DTO isn't part of the domain logic, is it?

Update: this is getting quite a bit of attention. A better use case regarding the promo code would be that users cannot enter any promo codes unless the value of the basket exceeds $50. That's a bit more clear rather than this being solely a UI issue.

13
  • 2
    Related answer: Validation in business layer vs. other layers
    – John Wu
    Commented Mar 22, 2021 at 17:16
  • 16
    very quickly you'll realize that you'll want to stop a user from inputting more than n promo codes, and now you have 2 pieces of business logic in the UI. That logic should live in a business object, from the start.
    – njzk2
    Commented Mar 22, 2021 at 21:19
  • 2
    The "business logic" implements decisions that your user experience designer doesn't get to make. Perhaps your application has no business logic. Many don't. Commented Mar 23, 2021 at 4:51
  • 2
    @MattTimmermans how would you define business logic? Another user defined it as "what your app does to data". Let's take another example - loading a truck. The client says the truck can only be loaded to 80% capacity. We must implement that in code. Is that business logic in your mind?
    – MSOACC
    Commented Mar 23, 2021 at 14:16
  • 2
    How do HTML does that? If we think a little, we can see HTML does exactly this, decouples view from logic. Content projection is the way to present business data through presentation elements. Commented Apr 15, 2021 at 13:51

11 Answers 11

56

When different people talk about decoupling the UI from the business logic, they sometimes mean different things:

  • They can mean not to implement any UI independent logic inside an UI layer - all logic which can be useful outside an UI should be placed somewhere else.

    Your example shows such a case. CanUserInputPromoCode may be useful out of the UI, or at least not restricted to a specific UI design. The most natural place for CanUserInputPromoCode is probably not a DTO, but a business object Basket. That will allow to reuse it in case the Basket object might get used inside a non-UI process (for example, in an automated test).

  • Or they mean to decouple the system from the specific UI technology. This can be realized by introducing architectures like MVC, or MVP, or MVVM, where there is an extra view model layer or presenter layer, which contains the UI controlling logic, but communicates with the UI through an interface (and so keeps the UI technology exchangeable).

    Note MVC, MVP or MVVM are not necessarily used for a UI technology exchange. For example in Web applications, designers often want or need the UI controlling logic on the server, whilst the UI uses HTML or Javascript on the client. Or they want UI controlling logic to become subject of an automated test.

And yes, UI controlling logic is also "business logic". Your shopping cart example may require a clearly visible "Pay Now" button of a certain size and color at the check out, before any financial transaction takes place. That kind of business logic cannot be decoupled from the UI itself, but that is not meant in Hexagonal architecture or Onion architecture when they speak about decoupling from UI.

5
  • Thanks. You mention "not to implement any UI-independent logic inside an UI layer" clicked. So, for example, not being able to submit an order with 0 items in the basket, that would be a business rule and would also go in the Basket class. You'd disable the "Pay now" button based on that rule. The user adds an item, your domain entity gets updated and returns to the UI which then updates based on it.
    – MSOACC
    Commented Mar 22, 2021 at 12:10
  • Can you think of any examples where you'd have UI logic that would be independent of business logic? Would it be something simple, like use a different font color when the basket item count is 0?
    – MSOACC
    Commented Mar 22, 2021 at 12:11
  • @MSOACC: I think this more a linguistic problem - as soon as we speak of "logic" at such layer, we usually have something in mind which counts as business logic. But I can think of non-functional UI properties which are clearly not business rules, like overall color scheme, low latency between pressing a button and getting a reaction from the UI, and so on.
    – Doc Brown
    Commented Mar 22, 2021 at 12:28
  • 18
    @MSOACC If you have an accounting tool, you might want to color all negative numbers red, and all others black. That is not what most people would describe as "logic", but instead "styling", even if you could reasonably argue that it is "logic for the displaying of numbers". Thats the kind of logic that is usually reserved for residing inside the UI, by necessity. Not being able to submit an empty basket is business logic, though. Activating a button (or disabling it) based on that rule is again, styling, though.
    – Polygnome
    Commented Mar 22, 2021 at 20:34
  • MVC is not the Microsoft Web Framework by the same name.
    – Joshua
    Commented Mar 24, 2021 at 18:41
44

You need to be clear about what is meant by business logic.

Business logic refers to the logic that act on the data.

It is not logic that validates the data.

It is not logic that reacts to the data.

It is not logic that displays the data.

Showing a list differently if count is 0 is not business logic; it is presentation logic. Ideally, it should not be implemented where you process the data to display, it should be implemented by the list component which does not even care what your data looks like.

While deciding how to display a list has zero business logic, there are some forms of conditional rendering that touches business logic. One example is what features a user can access depending on his role. In this case, given the data that is the user's role the system must restrict access to features.

Restricting access is an act. Since processing the user's role is business logic restricting access to features must not be implemented in the UI. It needs to be implemented in the core of the application (for a web or client/server app this is the back-end). The functions/methods themselves should throw an error if a user is not allowed to use them.

The UI can show/hide navigation items for features allowable to the user, but this should not be where the decision is made. In this case, showing or hiding what the user is allowed to do is presentation logic. It can be implemented purely in UI based on the user's role, in which case you're re-implementing business logic as presentation logic and, in my opinion, it's OK as long as it's not the only way you're restricting user's access to features. A more modular approach is to have business logic in the back-end return what the UI is supposed to render as navigation items. But that's not necessary in my opinion.

Another example is the updated question of not allowing the use of promo code if there is not item submitted with the order. Note that the real business rule is:

Promo code cannot be used with empty order

or

Promo code cannot be used if order is below $50

As such this business logic must be implemented in the back-end and the back-end must return an error if someone attempts to use a promo code with an empty order regardless if the order is submitted by the web UI, by an app, by a third-party app or by API access. As you can see, it is impossible to implement this business rule in the UI. It must be separated from the UI and implemented outside the UI.

Now, to improve the user experience you may also hide the promo code field if the shopping list is empty or below a certain value. As I have mentioned above, I don't have a strong opinion if this is implemented in the UI itself (basically reimplementing business logic as viewmodel logic) or have the business logic expose an API to tell the UI what to display. This is the same decision making process as the role/features example above.

While there are many ways to interpret what is meant by "business logic" I've found treating "business" as what your application does to data to be the most sensible. What data does to your application is not business logic. It is other kinds of logic — usually either validation or presentation.

Even a GUI-heavy application can separate business logic from UI logic. One good example is Photoshop. You may think that Photoshop must mix what you do to images with the UI but you are wrong. The UI is just one way to use Photoshop to perform actions like applying airbrush stroke here or selecting color there to images. However, you can do exactly the same thing in at least two other ways: inside plugins and with javascript scripting. All operations in Photoshop are not tied to the UI. The UI merely has access to the operations but you can also access the operations outside the UI.

12
  • 1
    @Polygorial Some frameworks and design patterns make this explicit even if some people don't realize it. Presentation logic is also known as the view model - that is, it is the model of the UI (view) not the data. This is hidden if something, this is red if something - you of course need code for the if and in OO-land that means a model - a view-model.
    – slebetman
    Commented Mar 23, 2021 at 12:47
  • 7
    Your understanding of the term "business logic" is clearly a very specific one, but it is not the only possible or correct one. For example, "validation" is something I guess lots of people would see as part of business logic. I think this answer could heavily improve if you add the words "In my opinion" or "According to my understanding" right in front of the second sentence.
    – Doc Brown
    Commented Mar 24, 2021 at 10:28
  • 2
    @MSOACC As mentioned in my comment above, "presentational logic" is usually called "view model" in OO. A view model is not a model of the business and is therefore not business logic. It is a model of the UI. I use the term presentational logic because not all frameworks are OO (there are a lot of functional frameworks in js) and in software architecture the general term for all of UI/UX is the presentation layer.
    – slebetman
    Commented Mar 24, 2021 at 11:08
  • 1
    @slebetman: and you think most people will find it there, somewhere hidden down below ;-) ?
    – Doc Brown
    Commented Mar 24, 2021 at 11:44
  • 1
    @DocBrown Yes. I expect people to read everything. Also because I think the fact that it is my opinion is not relevant because in my opinion this is the only correct interpretation in regards to the advice to always separate your business logic from your UI logic. All other interpretations cannot make sense when talking about the said advice
    – slebetman
    Commented Mar 28, 2021 at 0:46
13

You can't separate a UI from logic, but you can separate most to all of the "business" logic. Conditional rendering is fine, and you don't really need to involve business logic to do it. Rather than checking item count in your example, you could reference a "promoVisible" boolean. The DTO exists to transfer data, part of the data that needs to be transferred in most apps are the results of decisions like should a promo code get displayed. The real difficult part is separating validation logic from a UI, though even then the best practice is to validate separately on the back end since the UI can't be fully trusted.

2
  • Thanks, what you're saying basically chimes with what @Doc Brown said. Put the logical in the domain layer as a standalone property and transfer it out via a DTO. The issue you raise with validation is a good one as it's usually a good idea to do both frontend and backend validation, in which case our validation rules get duplicated. What's your solution to that problem?
    – MSOACC
    Commented Mar 22, 2021 at 14:07
  • 4
    @MSOACC, the solution to validation ranges from "None, just accept it" to "Use a library that is shared between front and back-end code". The latter is only really feasible when the front and back-end use the same language or if you can generate the validation code in both languages from a common description without human intervention. Commented Mar 22, 2021 at 14:15
6

Yes, it’s possible. The light bulb went off for me when I realized that “business rule” is not synonymous with “if statement”. You can have conditional rendering without specifying why the element is rendered (or not).

The shopping cart can have zero or more items and the promo code input can be included or not. From the UI’s perspective these are two independent concerns. As you said, you may pass a DTO to your UI that includes a list of shopping cart items and a flag for showing or hiding the promo code.

The business rule is that the state of the flag depends on the number of items in the cart. The UI doesn’t know about that business rule. It looks at the flag and it looks at the list and it’s oblivious to the fact that these two properties are connected.

8
  • Thanks, this is exactly how my thinking has evolved. However, others are saying the promo code example doesn't constitute business logic at all. Have a read at Flater and slebetman's answers and tell me what you think. They say that this is presentational logic. I guess this does beyond presentation though as (although the promo code example doesn't demonstrate it very well) this is a rule.
    – MSOACC
    Commented Mar 23, 2021 at 15:04
  • 4
    I call it business logic because the rule is dictated by a business person. What we call it is not as important as what it implies. The rule is likely to change, so I want to encode the rule in such a way that I can can easily change it with a low risk of unintended consequences. E.g. I’d rather change a method that adheres to the single responsibility principle than go digging around in HTML template code. Commented Mar 23, 2021 at 15:25
  • In support of your point made in comment, "business" is so vague that it can refer to business as in "not developer" (i.e. a business decision), "backend" (i.e. the business layer) or "non-view-related" (e.g. the JS/TS scripts/services in a complex SPA). "business" is relative to the speaker/topic of conversation. However, I do get the feeling that you're conflating SRP and DRY. SRP doesn't dictate that this validation rule should only be written once, SRP dictates that whoever validates shouldn't also e.g. persist data.
    – Flater
    Commented Mar 24, 2021 at 12:00
  • Thanks @Flater. Yes, when I first encountered the phrase "business rule" I understood it to mean "not developer". In those days, the "business layer" was called "middleware". Now "middleware" means something entirely different. You just can't win with these slippery terms! DRY -- don't repeat yourself -- states that the rule should be expressed only once. In addition to that, I want to make sure the expression of that rule is decoupled from other concerns. When I change the code for stakeholder A, I want to be confident I'm not inadvertently changing something that affects stakeholder B. Commented Mar 24, 2021 at 12:58
  • @PatrickMcElhaney said, "The UI doesn’t know about that business rule." One could argue that the UI should know about that business rule. Image a user with a $48.00 order who is looking all over for the promo code box, and quits without ordering due to frustration. The UI should indicate to the user that they have to spend $50 to use a promo code.
    – HardCode
    Commented Sep 8, 2021 at 16:09
2

Let’s say your ui should show or hide button X according to business logic. Simple: The business logic tells the ui from the outside whether to show the button. More precise, whether the user should be able to do an action Y that would be caused by pressing the button or not, it is up to the ui how this is accomplished.

Now the Ui doesn’t need to know about the business logic, and the business logic doesn’t know about the button.

Elsewhere this is known as model-view-controller pattern. The model contains the data. The view displays the ui. The controller uses business logic and data to control the view, and modifies the data according to actions in the view.

5
  • Is this really model-view-controller? I thought the model in MVC was about persistence (i.e. your database). The part that cares about how it is displayed would be your ViewModel. I think you are correct on everything else though. Some people seem to be saying "do the check in the UI, it isn't worth decoupling it". I disagree though and it seems you do too.
    – MSOACC
    Commented Mar 23, 2021 at 18:59
  • @MSOACC: The model is commonly sourced from the persistent data store when you have one, but that's not inherently necessary. For example, you could have an app that calculates X decimals of pi on the fly. That data (the digits) is the model, even though it didn't come from a persistent data store but rather an in-memory calculation. The model is simply "the data", without specifying where it originated.
    – Flater
    Commented Mar 24, 2021 at 11:55
  • The “model” goes in two directions: It goes to the database or whatever is used for persisting (or there isn’t even any persistence) and then it goes to the controller that controls the view, which doesn’t want to go directly to the database.
    – gnasher729
    Commented Apr 18, 2021 at 17:24
  • Same as I commented in another answer, one could argue that the UI should know about that business logic. Image a user with a $48.00 order who is looking all over for the promo code box, and quits without ordering due to frustration. The UI should indicate to the user that they have to spend $50 to use a promo code.
    – HardCode
    Commented Sep 8, 2021 at 16:11
  • In that case business logic tells the UI to display “spend $2 more to get 10% rebate”.
    – gnasher729
    Commented Jan 22, 2022 at 20:20
1

You're taking a much too literal reading on what constitutes "business logic", and by extension you're not mentally separating your domain objects from your viewmodels/DTOs either.

Frontend vs backend validation?

Imagine a shopping cart system. For whatever reason the client decides they do not want promo codes to be added to an empty basket (this maybe isn't a great example but run with me). In your conditional render in the UI you would have to check if items.Count == 0 - and boom, you've just implemented business logic rules in the UI.

If we forget having a nicer user experiences for a moment, that validation wouldn't even be in the UI.

Your frontend would allow the user to enter a promo code, the UI sends a request to the backend, and the backend will throw an error. That is the baseline behavior you want, because it is the most secure. If you were doing those checks only on the frontend, the customer could find a way to circumvent them since they can mess around in their own browser. But they can't mess around with your backend, making it the ideal line of defense against enforced validation.

It's secure, but it's not very user friendly. The user spent time and effort filling out your form, only to then be met with an error that they could've been informed of minutes earlier when they were still filling out your form.

This nicer user experience is achieved by having the frontend perform additional validation (of the same rule), which it can do in real-time so it can alert the user immediately.
If the user somehow decides to circumvent that frontend validation, then they can probably find a way to send that (invalid) request to the backend anyway, but the backend will still perform its own checks and reject the invalid request anyway.

If you're allergic to writing the same validation twice, then only do backend validation and accept the decrease in user experience.

"Business logic"

Business logic is a very abstract term. Commonly, it refers to what the backend does, not the frontend. However, for a sufficiently complex frontend app, you might have a frontend dev team who refer to the JS/TS scripts of the frontend app as business logic (as opposed to the views of the frontend app).

You're clearly in the first camp here, specifically referring to the business layer of the backend service. In this context, business logic happens on business objects.

Business logic vs view logic?

Your frontend should never receive business objects, since you're doing proper layer separation (onion, hexagonal, ...). Your frontend is working with viewmodels or DTOs, which are not business objects.
By that definition, any logic that operates on these viewmodels or DTOs is not business logic, it is view logic.

As we established before, what a backend dev might refer to as "view logic", a frontend dev could refer to it as "business logic", because the frontend is that developer's business. This is a matter of subjective scope based on who you ask.

Based on your question, it seems that you mentally haven't quite separated your layers the way your code has already separated them. You're still thinking of your viewmodels/DTOs as business objects, which they aren't.

You cannot reasonably expect to have the frontend do literally no logic. Otherwise, what would be the point of JS/TS or any frontend framework?

3
  • Thanks for your detailed reply. I am in no particular camp, I am trying to learn. In this example I can envisage a scenario where the UI is totally dumb and implements no logic. Each time the user adds / removes / creates a cart, the BL layer generates a DTO with the CanUserEnterPromoCode value set. The UI conditionally renders based on this, but the rule itself (is the item count == 0) is implemented in the backend. The user adds an item, a new DTO is returned, the UI updates.
    – MSOACC
    Commented Mar 23, 2021 at 14:32
  • Of course we validate on the backend as well as having the frontend restriction, so this would result in the rule being implemented in two places. Probably we can live with that. This gets into a wider discussion on what BL even is. Another user defined it as "what your app does to data" so in this case the promo code isn't BL at all.
    – MSOACC
    Commented Mar 23, 2021 at 14:34
  • @MSOACC: The decision to not show the promo code box with empty shopping cartsis not business logic. The decision to not allow promo codes to be applied to empty shopping carts is business logic (validation) that should happen on at least the backend, and possibly the frontend as a UX measure. But I get the feeling there is a lot of architectural gap here, e.g. why your backend would be needed to return an empty shopping cart to begin with. Shopping carts (pre-order) are generally a frontend concern, with storing it on the backend being a non-essential nice-to-have for UX reasons.
    – Flater
    Commented Mar 23, 2021 at 15:27
1

I think that this discussion is stumbling over what is meant by the term, "business logic."

The analogy that I like is this: "You can discuss 'business logic' with a businessman, making no reference at all to a digital computer." You can implement "business logic" using paper and filing-cards, like they used to do a hundred years ago. Computer software at various levels implements that "business logic," but the users who are using the system to perform their jobs are following that "business logic" also, in their own human activities.

1

Your confusion arises from the poor state of language and definitions in the industry. Here, I will break it down for you, then apply to your example.

What is business logic

To answer your question, the language and ideas need to be clarified first. Business Logic is actually quite abstract and broad. It's clearer to reason about "System" Logic, which itself can be broken down to concrete ideas:

  • Authoritative Process Logic - changing state of the data (let's simply imagine a single database), and enforcing rules (eg. validation). This is minimally required, you cannot only rely on the UI.
  • Authoritative Data Access Limits - who can access what.
  • UI Guiding Logic - catch user mistakes as they happen. (eg. UI validation)
  • UI Access Logic - The UI might hide a section of the UI depending on the user's role.
  • UI Behavior Logic - guiding the user through data. Animations. Wizards, and Steps.
  • UI Human Formatting - presenting data in a way that's ideal for humans to work with. (Thanks @gnasher729)

A backend/server is an authoritative agent that can enforce rules, because there the code is isolated from modification from the user. If you only rely on the UI, then it's possible to use the API in an invalid way. The backend IS the system. The UI is the remote control for the user. If you can duplicate logic in the UI, then it becomes more efficient for the user - a smart remote control.

Applied to your example

The problem I have is that I find this quite difficult to imagine in practice. The UI will likely implement conditional rendering, and much of that conditional rendering is a business rule in itself.

This is simply UI Access Logic. The UI cannot enforce that, only a backend (which is an authoritative agent) can authoritatively enforce access rules.

Imagine a shopping cart system. For whatever reason the [shop] decides they do not want promo codes to be added to an empty basket (this maybe isn't a great example but run with me). In your conditional render in the UI you would have to check if items.Count == 0 - and boom, you've just implemented business logic rules in the UI.

That's UI Guiding Logic.

Or would you have this rule in your DTO with a property called CanUserInputPromoCode? Even then, the DTO isn't part of the domain logic, is it?

Let's just say "Backend". This is Authoritative Process Logic. From an enforcement perspective, it doesn't matter how you structure this in the backend, as long as it's in the backend somewhere, secured and only modifyable by the shop vendor.

Update: this is getting quite a bit of attention. A better use case regarding the promo code would be that users cannot enter any promo codes unless the value of the basket exceeds $50. That's a bit more clear rather than this being solely a UI issue.

The answers above remain the same.

In conclusion: the information about "architectures" is not helping unless you understand the elementary principles of how system logic is used in enforcing business rules. The rest is lower level design decision-making.

2
  • What can belong into the UI is displaying dates as “Today”, “Yesterday”, “ June 12th 2015”, possibly withe the business deciding which ruleset to use.
    – gnasher729
    Commented Jan 22, 2022 at 20:23
  • @gnasher729 agreed - "Human Formatting" is an important role that the UI performs. Commented Mar 15, 2022 at 23:46
0

You can't fully separate UI and business logic.

In many cases, your BL exists mostly to support the UI. Making BL really UI agnostic would mean implementing a lot of code paths that are never taken. Dead code is bad.

Conversely, a BL unaware of a specific UI needs will cause reimplementation of whatever behaviors fails to be exposed. Duplicate code is bad. Inappropriate intimacy is worse.

Decoupling is about making sure the interface between BL and UI is sane and designed in a way that allows change and testing of one without the other (ideally), or (practically) with limited awareness of the other. Because separation of concerns reduces complexity, and complexity confuses people, leading to bugs.

Typically:

  • BL doesn't depend on any UI code, libraries or shared state
  • Correctness is always implemented in the BL layer
  • Business behaviors required by the UI is exposed as an API by the BL layer

In your example, if the rule is: "promo codes cannot be added to an empty cart", and you want your UI to hide that element if the cart is empty, you would:

  • Have a BL API like canEnterPromoCode(), implemented with cart.isEmpty(), with tests and the works
  • Have the UI render the promo code input based on canEnterPromoCode()

...because the UI doesn't need to know under which conditions promo codes are allowable, just whether to provide an input or not. And other options have their own issues the moment you want something like BL.addPromoCode(…): you'd want that method to fail if the cart is empty, but you can't do that without a BL->UI call or code duplication.

2
  • 1
    You mention the risk of inappropriate intimacy but I don't think that applies here. I'm also not sure we need a full API call just for this. If we model our Basket object and have CanUserAddPromoCode as a public property, set by logic within, anything else that uses it (like something building a DTO based on the Basket) isn't inappropriate so long as Basket has been modelled well. We can return that DTO and do the conditional rendering based on CanUserAddPromoCode. Do you disagree?
    – MSOACC
    Commented Mar 23, 2021 at 19:05
  • Agreed. An effectively RO public field in an object or a getter API are functionally equivalent, and just a matter of style. Inappropriate intimacy would be the UI having to dig into internal basket state, user properties, or wether any item in the cart has open promos.
    – ptyx
    Commented Mar 24, 2021 at 16:25
0

Update [to example scenario]: [The] users cannot enter any promo codes unless the value of the basket exceeds $50.

Your example case certainly qualifies as business logic (latest since the clarified example), that itself however does not necessarily imply your UIs conditional code being relevant to the business process.

Security

Your code (items.Count == 0) itself is being transferred to the client and thereafter executed on the client's machine, i.e. in an entirely uncontrolled environment. Regardless of how well-written1, unit-, integration- and e2e-tested it may be, it's in the nature of the runtime env that it can be tampered with at will.

This is why it cannot make important decisions. Ever.
It's not the Javascript, it's not the engineers, it's the execution environment.

The decisive evaluation of the condition for promotions being fulfilled must happen server-side during the checkout step of the sales process.

Decoupling

The attempt to literally absolutely "decouple" a user interface from a server-side process, both of which are meant to model the same real-world process (a sale), has - in my limited experience - always consumed vast amounts of resources that could have been allocated much more sensibly. I was once witness to a sad, slow, painful death in the vastness of the desert of layers of abstraction, useless indirection, and false generality. It wasn't fun to be a part of at all.

Deduplication

Security aside, shipping your code to production as proposed will lead to a regression in overall maintainability of the software.

In your example, the predicate that must be true for a promo code to be valid is net total sale exceeding the threshold of $50. Easy enough a condition to check.

  • What if management decides to change the threshold value?
  • What if the predicate is being repeatedly extended over time by further conditions?
    (the customer must have been registered for period n, the annual total spend of the customer must exceed m, e.g.)
  • What if the threshold is specified to become a derived value of sorts?
    (say, $50 on a rainy day, $10 when it's sunny out and $200 the week before the holiday's)

Or would you have this rule in your DTO with a property called CanUserInputPromoCode? Even then, the DTO isn't part of the domain logic, is it?

No. That wouldn't be much better. (Though better.)

The predicate condition(s) in the UI code should be statically generated during build-time from the backend source code directly or from a shared, version-controlled configuration file.

Iff requirements ever become so dynamic that the predicate must be variable at runtime, then the client application should fetch the latest condition(s) as needed from a dedicated server-side endpoint (or from a dedicated server-side query resolver, for the cool kids in the GraphQL camp).

A fictitious, but realistic scenario

In terms of the layering of the codebase and day-to-day interdependence between the work of UI engineers, backend engineers and your DevOps team, go ahead, decouple away.

Generally, I am rather flexible on the topic of language / stack choice, too.
However, some language(-family) that supports Generics (Java, C#, Typescript), ideally static, compile-time polymorphism (Rust, C++) for the server-side is invaluable.

Again, deduplicate, automate, abstract.

I want a significant part of my runtime code to be generated. I don't want to see macros or a templating language (moustache, handlebars, ejs, etc.) in my codebase either though. Generated code should be compiled, type-checked, tested - it deserves all the usual engineering goodness the rest of your codebase (hopefully) enjoys.

Say some team member implements a new kind of HTTP response never used in the software so far. They add HttpStatusImATeaPot = 418 to enum class HttpStatusCode {} in the backend's HTTP utility library and use it in an endpoint.
During build, the same key/value should be automatically added to a generated Typescript enum of the same name. At this point, the UI's development build should issue a warning on an exhaustive check that an enum value is missing from a switch statement in the client http response handler. CI builds should fail with an error. Continuous Deployment should exclude the merge until the UI has caught up.

In case of compilation misconfig (e.g.) and a build having succeeded that shouldn't have, an end-2-end test before deployment should verify the HTTP responses of the runtime code.

This isn't a one-off requirement either. I want this to be available to the UI team for every single <InsertFancyBusinessProcessName>(Rejection|Success)Reason. For every subclassed error or exception that is intended for the client.

--> How are you going to "decouple" that?
--> The entire premise is faulty, I believe.

N.B. I am not advocating for any significant amount of runtime symbols /symbol names to be shared. Literal values and types though? yes please!

Conclusion

Integration and end-to-end tests, for instance, are not (superfluous) tight coupling. On the contrary, those are a pre-requisite for the long-term success of a project.

Having a common foundation (shared static values, depending on languages involved shared library code) between layers is not tight coupling at all.
That's simply sane architecture.

If UI and Backend are so professionally "decoupled" indeed, that UI engineers are manually duplicating semantically meaningful literals, which already exists on the other end of an API, then something's gone in an entirely wrong direction.
If they are "so totally decoupled" from the API that they must maintain their own adapter code for it, whose benefiting, exactly?

1 Sidenote: Use strict equality checking in ES.

13
  • Thanks for your reply. I certainly wouldn't be running the promo code check only on the frontend. If you check Patrick McElhaney's brief answer, he basically arrives at the same point I have. Run the rules on the server and return a DTO with the CanUserEnterPromoCode. Don't run the check in the DTO, but just have the DTO return that to the frontend. The frontend then just does the conditional rendering based on that value. In regards to decoupling the UI and the domain, this seems to achieve it to a fair degree.
    – MSOACC
    Commented Mar 23, 2021 at 18:49
  • That same DTO could be fed to another UI - say a mobile app - that would be able to implement it again without concerning itself with the business logic of why the promo code cannot be entered. Therefore, we have achieved a good level of decoupling. This, after all, is a big selling point of striving for loosely coupled code in the first place and is what onion architecture aims for. I really wouldn't call this attempt at decoupling a waste of time. What are your thoughts?
    – MSOACC
    Commented Mar 23, 2021 at 18:49
  • Sounds reasonable to me, @MSOACC. On returning a flag with a "DTO": Matter of taste in most scenarios. Iff one's got accurate measurements, a more informed case by case decision is possible. A scenario where you'd definitely like to split the API requests is a highly distributed system. Especially when a third party is involved. Suppose the promo code may only be approved for customers with a high enough credit score and your server's backend has to not only consult the local database cluster, but also call the legacy Acmefax API, which returns XML Soap with a latecy of up to 20sec. Commented Mar 23, 2021 at 20:53
  • On multiple clients: Sure. Hence the need for a maintainable single source of truth. On the zero concern for the why: Cannot concur. In a contrived example, you'll get away with it. Good UX demands informing the user. Rarely is a boolean flag sufficient to render a response. As a consumer in 2021, are you going to give an online retailer that can't communicate better than "Payment failed" a 2nd chance? More often than boolean values do you need shared enums. On the "waste of time": Both very matter-of-fact and rather judgmental phrasing on my part. Revised. Thanks for pointing that out. ;) Commented Mar 23, 2021 at 21:05
  • OK, so when you say that an attempt to totally decouple a more complex UI from the BL is a waste of time I can see where you're coming from if you have an enum of possible outcomes and you need to display a result for each of them, and each result then opens up another possible path the user can take. That is business logic. So you'd be willing to accept that level of coupling?
    – MSOACC
    Commented Mar 23, 2021 at 21:44
0

The UI is business logic already, if you think about it. In fact it is as crucially part of the domain as the rest of it, at least in principle. The UI structure should and normally does reflect the overall structure of the system. Elements and structures in the UX, both spatial as well as temporal, both explicitly defined as well as implicitly observable, already inherently reflect those in the whole system, even if not in a reified/codified way. Additinally, design elements and themes are often driven by or reflective of domain requirements (think shopping cards and global holidays, special sales, etc).

All in all: the UI always evolves alongside the “pure” domain logic, and domain logic must live up to and evolve according to newly discovered or changing realities at the UI level.

So why shouldn’t we view the UI as a core part of the domain? Of course we shouldn’t normally leak HTML and CSS into our “pure domain” logic, but UI rules, navigation structures, events, not to mention dynamic contents — all are already unavoidably “coupled” to the domain, even if not in obvious ways by today’s standards.

A lot of modern languages can be transpiled into Javascript nowadays, the browser can store lots of data, even for offline processing. We have websockets and peer-to-peer data solutions (e.g. GunDB). Node.js can render stuff on the server side with headless rendering. Expected screen layout can be known prior to the HTTP response being sent, feedback from the layout could be incorporated back into some domain logic processing once or more —i.e. domain logic could use headlessly rendered subparts of the final UI output as a sort of constraint engine or reality check before finalizing the results. For example for finding the optimal way in real time to present product search results based on whatever domain criteria currently active — no need to sync with the HTML/CSS team. Entire changesets of domain and/or UI logic could be played out with automatic analysis of outcomes, if the UI was modeled as an integral part of the domain from the grassroots. We’re seeing a melting together of the server and the client, for those that want to leverage the possibilities.

Why not design our domain code to already incorporate the user interaction into the big picture from the grass roots, and then simply add a UI layer to hold only the topmost final rendering bits. So there would be an ubiquitous model that covers all aspects of the system, a registry to specify how to render which combinations of user-facing state, and a single controller, or engine, to make the UI alive, to stitch everything together dynamically (and generate any client side code if necessary), as opposed to multiple/many ad-hoc controllers.

Think in the lines of Direct Manipulation Interfaces, but even navigation and layout could be seen as Directly Manipulable aspects of (transient or persistent) domain state. Check out the ‘surrogate objects’ concept in https://engineering.purdue.edu/~elm/projects/surrogate/surrogate.pdf.

As you add concepts and relationships to your domain, the engine tries to glue everything together, finds the holes introduced by these newly added denizens of the domain landscape, and informs the team which new state combinations need additional rendering elements (widgets?) in order for the UI to be complete. You implement those, and the UI “gets synced” for free. Why would you still want a rigid MVC layering then?

Not the answer you're looking for? Browse other questions tagged or ask your own question.