9

I've been reading a lot of material lately about DDD (business entity objects) and other common patterns in n-tiered(layered) architecture. One thing I have issue with is, most articles, blogs, examples, etc. seem to talk to only one aspect of a system. Some may talk about creating a BLL for containing business logic. Some talk about only saving data via a DAL (ignoring reading). Some talk about only DTOs. I've not yet found anything that at a basic level talks about putting all of this together. When I attempt to put a lot of that material out together myself, it seems the information is at times is mutually exclusive.

Here is where (to me) things tend to be mutually exclusive:

  • Business logic is contained in the DDD business entity objects, which your UI doesn't create directly (it gets DTOs).
  • Data is passed to the UI via DTOs, which should not contain business logic. The DTO should only contain getters and setters.

Which leads to my Question:

How does your UI configure itself when business logic is required to analyze the data of those DTOs to make UI decisions?

For example, you are developing the UI and you have this requirement. You need to determine in code whether or not the Delete Button should be enabled:

System Requirement: An Order can only be deleted when there are no products on the Order and the Order's status is 'Open'.

Sure, you can do a check in your UI with the following:

Dim _order As OrderDTO = SomeServiceCall.ReadOrder(123)

If _order.Products.Count = 0 And _order.Status = "Open" Then 
    DeleteButton.Enabled = True
End If

But isn't this now putting business logic into the UI code? What if there are new or changed business rules to determine when an Order can be deleted? While you could add a function .CanDelete() to the OrderDTO, it's the wrong place based on everything I've been reading, because business logic belongs in the Order entity object. So how do you tell the UI to Enable or Disable that darned Delete button???

One option may be to always enable the Delete button and throw an exception when the Delete action fails validation in the Order entity object (side note: just found FluentValidation and it seems amazing!). But that's sloppy UI. What are the "good architecture" alternatives to get the UI what it needs?

The same could apply to many other UI situations, where based on different combinations of the data (i.e. business logic) contained in a DTO, you may lock out some input fields, or hide them, etc.

A lot of times it seems the write-ups about DDD, DAL, BLL, DTO ignore the practical UI requirements.

  • I think the crux of this question is the sentence - "Data is passed to the UI via DTOs, which should not contain business logic." - where did you find this, to which architectural context does this idea refer? What does "UI"in this sentence mean precisely (for example, in a MVP architecture, does this include the Presenter layer or not)? And why should a presenter layer not directly access business entities and their methods? What kind of architecture is this? – Doc Brown Oct 26 '19 at 8:24
5

System Requirement: The UI should only enable the Delete button for an Order when there are no products on the Order and the Order's status is 'Open'.

This is the classic requirement mistake of letting implementation details leak into a requirement. To the point, this requirement has absolutely no right to know that the way a user requests a delete is by using a button. The requirement isn't a UI requirement at all.

The most fundamental question in software design is, "What knows about what?"

It's the UI's job to know that there is a button. It is not the presentation layers job. The presentation layers job is to know that there is a command feature being disabled. Doesn't know if it's a button. Doesn't know if it's labeled delete, effacer, or löschen.

Isolating knowledge it the point of all this. So yes. That code shouldn't be in the UI.

But .CanDelete() is a broken strategy simply because it fails to present enabled/disabled status to the user before they issue the command (however they issue it). That's your UI requirement.

Enabling and disabling in the UI doesn't require business logic in the UI. It only requires that the UI accept status updates whenever they are sent. It doesn't need to understand why.

So unless you want to poll by infinitely looping over .CanDelete() what you should be doing is calling this business logic whenever _order.Products.Count or _order.Status could have changed state.

You want to put this all together? Start with something that works. Then spot places where too many ideas have been mixed together and tease them apart. Do that enough times and you'll start seeing what to seperate at the start and save yourself some time.

It's very important to start this way. If you only ever build systems whose concerns are perfectly separated you'll never learn how to fix systems that have them mixed together.

  • Yes, referencing Button in the requirement is not something I do in practice. Mistake in my post on my part. I'll edit that. – HardCode Oct 24 '19 at 13:42
  • "It's the UI's job to know that there is a button. It is not the presentation layers job." This is the first time I'm seeing the UI referenced as something separate from the Presentation Layer. I've always thought the UI is the presentation layer. Can you explain further or have a reference i can read? I'd appreciate that. – HardCode Oct 24 '19 at 13:43
  • @HardCode look at an OSI stack. Presentation and UI are not the same. A model knows what number you have. A presentation knows to express it as 3.14159 or π. A UI knows to express it with a text box, or a console, or an audio speaker. You don't have to know all these things in the same place. – candied_orange Oct 24 '19 at 15:53
  • @candied_orange, it seems to me a somewhat artificial distinction. The presenter in this case really appears to be doing very little, and I couldn't imagine a presenter that truly worked unchanged whether the UI was a console or a GUI, because different UI technologies have different idioms and would integrate differently. – Steve Oct 24 '19 at 16:48
  • @Steve all distinctions are artificial. You don't have to do any of this. If you separate UI and presentation code you make that separation really valuable if you do it with a design that allows you to substitute one UI for another. But that value doesn't come for free. You have to do design work to create an API that works for all the ones you wish to support. It's not impossible. It's just work. – candied_orange Oct 24 '19 at 17:42
3

What if there are new or changed business rules to determine when an Order can be deleted? While you could add a function .CanDelete() to the OrderDTO, it's the wrong place based on everything I've been reading, because business logic belongs in the Order entity object.

What works for me: I think of the domain model as a finite state machine. The state machine reacts to messages, where messages can be simple things like a "Time Has Passed" with a reading from a timestamp of a local clock, or something complicated like a booking agent selecting a specific itinerary for cargo. The state machine copies the information that it wants from the message and computes its next state (which could even be the current state, if there is no transition or if the transition loops back to the current state).

When the model describes its current state (aka providing a view for the use case of change), it does so by not only offering a representation of its computed state, but also a representation of the candidate commands it is prepared to receive.

In your specific example, that list of candidate commands for an empty order would include both the "add item" command and the "delete order" command, but in the case where the domain logic forbids the delete then the delete order command is not present in the candidate list.

It is then, as @candied_orange pointed out, someone else's job to figure out how to translate each of the candidate commands into the appropriate user affordances.

For example, in a Web interface, you might have a module that takes the list of candidate commands and creates HTML forms for each. It's not "domain logic", in the sense that it isn't making the decisions of the domain model, but it is translating from the domain models message to a more general purpose representation.

But you could just as easily replace the web interface with a command line interface; the list of candidate commands is unchanged, but the expression of them in the command line interface is certainly different.

In a sense, adding ".CanDelete" to the DTO is exactly the right sort of idea, although might not use that spelling. Your DTO is an immutable data structure with some in-memory representation of the list of candidate commands, and you can choose whatever design you like for querying it.

Dim _order As OrderDTO = SomeServiceCall.ReadOrder(123)

If _order.commands.contains("http://example.org/commands/deleteOrder") Then 
    DeleteButton.Enabled = True
End If

Remember, the basic idea of a Data Transfer Object is that it is designed to reduce the number of method calls, which is to say it has many interesting answers baked into it. So we are encoding some redundant information into the DTO, and in return we get centralizd domain logic that is easier to change.

A lot of times it seems the write-ups about DDD, DAL, BLL, DTO ignore the practical UI requirements.

Yes. DDD in particular is bad at discussing "plumbing", and the real complications that it can introduce.

1

Given that the DTO does not need to be 1 to 1 with the data entities, I'd go with your CanDelete() in the DTO using the business rules in the BL. I'd probably repackage it as an attribute "IsDeletable" to make it more data-like.

If this is a regular pattern then you might want to embed a structure of some sort. (IsDeletable, IsUpdatable).

If you were doing an API instead of a UI (and an API is a type of UI in my opinion) it's kind of like https://en.wikipedia.org/wiki/HATEOAS in abstract.

There's a lot of dogma as well as perfectly valid practice that is out of favour. Picking that which works for you and allows you to deliver something while striking the balance between perfectionist hand wringing and hacking is a skill.

1

I feel your pain, and I've made the same observations couple of years back, that led me down the rabbit hole of re-evaluating what object-orientation supposed to be and what constitutes a maintainable design. Short answer: You can't find a lot in this subject, because it doesn't work. N-tier architectures, DDD (as practiced by most) and DTOs in particular are all sub-optimal ideas. Persistence-agnostic business-layer doesn't work. Business-agnostic UI in these designs doesn't really exist.

First: It is entirely reasonable to have a requirement to gray out some buttons based on some rules. The UI is what users see, it is completely natural for them to adopt the terminology of the UI.

How to implement: Let's think about this one step at a time. Where is the knowledge supposed to be to determine whether an Order is deletable? It seems pretty reasonable to expect this in the Order itself. It depends on the internal state and semantics of the Order, so it should be there.

So how does the information "get to the UI" from the Order? This is where all the above designs fail. All of them try to get this information out of the Order somehow. And regardless of how you do that: whether it is a new field in the DTO, whether it is status updates or events, or whatever, it always means that now you couple all those things to this simple feature.

The only way to get this in one place and maintainable is to not get this information out of the Order at all. Instead, get the behavior that needs this information into the Order, and that is to present the Order on the UI.

So it follows quite reasonably that the Order should be able to present itself, with disabled/enabled delete button and everything. In effect, the UI should not depend on the "Business", the "Business" should depend on the UI. (Or the "Business" has a UI if you will)

I hope that sounds entirely reasonable to you too, even if it runs counter to almost everything we've been told by these architecture and design patterns the last 2-3 decades.

Update pseudocode example:

class Order {
   ...state: i.e. products, whatever...

   void cancel() { ... }

   ...

   UIComponent display() {
      return Panel(
         new Table(products),
         new Button("Track", ...),
         new Button("Cancel", products.empty?ENABLED:DISABLED, this->cancel()),
         ...);
   }
}

This demonstrates what I mean. The Order knows how to present itself. This follows KISS, Object-Orientation, Law of Demeter, is Maintainable, etc. If the "Business" and "UI" is in the same application, there is no reason whatsoever to not do it this way.

Note that the decision whether the order is cancellable stays within the Order. Note also that there are no details of the presentation leaking into the Order itself. No colors, layout or things like that.

  • "The only way to get this in one place and maintainable is to not get this information out of the Order at all. Instead, get the behavior that needs this information into the Order, and that is to present the Order on the UI." Could you expand on this, with maybe a small concrete example? – HardCode Oct 25 '19 at 14:07
  • So do I understand this correctly, you are suggesting to have no distinction between a business layer dependent and an UI layer and mix them up into one? – Doc Brown Oct 26 '19 at 8:15
  • @DocBrown I don't think that's a fair characterization of what I've said. I would argue exactly the opposite is true. The distinction between UI and Business is much stronger than before. Here the UI components don't know about the internals of the Order. Not the properties, not how it all relates, not even what length are the individual data elements, etc. This is exactly what was asked above. In popular architectures it is exactly backwards, the UI knows everything of the business. So if "business" stuff changes, you likely have to modify both layers. – Robert Bräutigam Oct 26 '19 at 8:27
  • Ok, so you are suggesting to have a dependency from the BL to the UI layer (or layers, if there is more than one of them), but not vice versa? – Doc Brown Oct 26 '19 at 8:36
  • Yes, that is what I'm suggesting. Note though: The UI in such an architecture will not look like anything we would call "UI layer" in "traditional" designs. It will mostly come from a generic library, with some additional things like Pages, some standard Widgets defined, that is it. If it is a web-ui. – Robert Bräutigam Oct 26 '19 at 9:27
1

I am not sure which kind of architecture you had in mind with the idea of using DTOs this way, but let us take Bob Martin's very popular Clean architecture and have a look how the problem is solved there.

In such an architecture, an UI object would provide an interface for

  • getting Order data and for

  • enabling and disabling buttons (but without any logic how and when to do this).

A Presenter object (which holds such an interface instance) could grab the data from the order entity, pass that data - maybe as an order DTO - to the UI through the interface, check the .CanDelete() method of the order entity and call the interface to disable or enable the delete button accordingly. The presenter can also listen to certain events in the system which may change the state of the order, and repeat the evaluation of .CanDelete() (and the update of the UI) then.

So, in short, I don't know which "articles, blogs, examples, etc." you are referring to, but the ingredient which seem to be missing there are simply interfaces and events. The "Data is passed to the UI via DTO" idea may be simply a misunderstanding or oversimplification, but since you did not gave any references I cannot actually tell you what the source where you got this from really meant.

  • I've read so much, I never kept links to cite. I think a lot of the material I've read is oversimplified, which leads to my confusion about these things. One concept I'll have to look into is Presenter, to maybe help make more sense of my core question. – HardCode Oct 28 '19 at 15:01
  • "I am not sure which kind of architecture you had in mind..." That's what I'm still trying to figure out :-) – HardCode Oct 28 '19 at 16:06
  • @HardCode: I suggest you read the article about the "Clean architecture", especially the ideas of how data crosses boundaries between the layers, and which role interfaces play. If you think this still does not answer your question, feel free to ask. – Doc Brown Oct 28 '19 at 16:16
  • That's one blog post I have read. But, I'm more of a "read the code" kind of person, so while that post of theory might be good, without concrete code examples to back up that theory, it gets lost on me. – HardCode Oct 28 '19 at 16:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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