34

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.

8
  • It would definitely be the second option. But I don't understand your argument with DTO and DTO not being part of the domain logic. – Euphoric 2 days ago
  • I'm asking a question re:the DTO rather than making an argument. – MSOACC 2 days ago
  • 1
  • 6
    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 yesterday
  • 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. – Matt Timmermans yesterday
28

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.

4
  • 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 2 days ago
  • 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 2 days ago
  • @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 2 days ago
  • 12
    @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 yesterday
17

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.

15
  • I really like this distinction between business logic and presentation logic. Haven't thought about it this way before, but it makes sense. – Polygorial yesterday
  • @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 yesterday
  • @slebetman Thankyou, I echo what Polygorial said. I have yet to encounter presentational logic be mentioned when reading about onion architecture (it may well be though, I am no expert at all). However, in your example you mention duplicating the access permission logic in both the frontend and in the backend (where app-level, rather than UI-level, restrictions are implemented). Why don't you consider this an issue? Why not have the BL generate an object and return that to the UI which just implements it rather than saying "if user is admin show x else y" and so on. – MSOACC 23 hours ago
  • I realise we're not meant to have drawn out chats here. I've made this room, anyone else can chime in too: chat.stackoverflow.com/rooms/230268/… – MSOACC 23 hours ago
  • Also, just to re-iterate the promo code example... this isn't a good example. A better example may be that we cannot apply any codes when the total value is less than $50. That goes beyond presentation - it's an actual rule. That does fit into your definition though - our app is doing something to that data, and part of that something is not processing the code based on the total value of the chosen items. – MSOACC 22 hours ago
11

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 yesterday
  • 3
    @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. – Bart van Ingen Schenau yesterday
1

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.

2
  • 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 19 hours ago
  • @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 2 hours ago
1

A lot of discussion has gone on around this topic and I want to summarise the themes that have emerged.

For a start, I now realise that the question was all wrong. This isn't an issue of coupling, it's an issue of separation of concerns and duplication.


Coupling

The definition given by Wikipedia is:

In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are.

So implementing the promo code logic (if total value of basket > $50 allow promo codes) in the UI is not a coupling issue at all.

What the issue does pertain to is a separation of concerns and a duplication of business logic (i.e. DRY). In this simple example of the promo code, I and several others agreed that the easiest way to implement this would be to have the business logic (probably occurring on a server) implement the logic surrounding promo codes, and for that business logic layer to return a DTO containing a CanUserEnterPromoCode boolean. The DTO is dumb, and the UI's conditional rendering is based on this boolean. Therefore we have removed the duplication of having the BL in both the backend and frontend.

To mention coupling again, since the UI depends on this DTO we can say that the UI is coupled to it. It relies on that DTO; if the DTO changes, so must the UI. Importantly, the business logic can change without the DTO / UI changing. We can change the minimum basket value from $50 to "$100 and 5 or more items" without the UI knowing.

As complexity grows

This is all good until we reach a more complex example. Allowing or disallowing a promo code based on the number or total value of items in a basket is a simple boolean expression, but how often is that the case? In reality, we're likely to have multiple different user paths that can be taken depending on various properties in the DTO returned to the UI.

Implementing those paths - allowing the user to do x because they already did y - is business logic. Trying to fully separate these concerns so that the UI is totally dumb, doing nothing other than implementing dozens of true/false statements, may be more trouble than it is worth. A better approach may be to create an enum in our business logic layer that defines the paths that the user can take, and have these implemented in the UI. Some of this could be presentational logic as others have said, but in reality we may end up implementing some business rules in the UI. What matters is that we minimize this as much as is feasible and enforce our rules on the backend (i.e. never trust user input). The ideal scenario is to maintain a single source of truth. I can see no consensus on the degree to which this should be adhered to (i.e. how much logic we duplicate on the frontend), but I want it as much as possible.

Business logic

There were also discussions on what BL actually is. Many basic CRUD apps have little to no business logic. A rough definition seems to be that "business logic is logic that encodes real world rules in software" - things like:

  • Don't allow promo codes if the basket value is < $50
  • Don't fill a truck to more than 80% full

This answer is compiled from my discussion with others as to save people from having to read every single answer and comment. Disagree with me if you like and I'll update the answer if you can demonstrate it, but this seems to satisfy my original question. Use the chat room here so we don't end up with dozens of comments.

1
  • You mention some "business logic" may unavoidably get into the "UI". How about the other way around? What if there is no "business logic" in the UI, but instead some UI may get into the "business logic"? – Robert Bräutigam 2 hours ago
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.

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 18 hours ago
0

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 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.

4
  • 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 22 hours ago
  • 2
    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. – Patrick McElhaney 22 hours ago
  • 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 2 hours ago
  • 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. – Patrick McElhaney 1 hour ago
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.

New contributor
Johannes Pille is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
10
  • 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 19 hours ago
  • 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 19 hours ago
  • 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. – Johannes Pille 17 hours ago
  • 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. ;) – Johannes Pille 16 hours ago
  • 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 16 hours ago
-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 23 hours ago
  • 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 23 hours ago
  • @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 22 hours ago

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