2

enter image description here

Assuming this type of architecture, whether you call it clean, ddd, onion, or hexagonal, where the dependencies point inward and the application logic is meant to be heavily decoupled from the UI / presentation.

How do i handle something like showing a popup? A popup would have to be actually implemented in the presentation layer obviously. But the classes / use cases / services / the actual logic that would need to display a popup and act according to user input, lives in the application layer.

So i create an interface IPopUpService in the application layer. Suddenly i have access to to it in the persistence layer. I can literally put popups into my database queries. This seems like a pretty big flaw.

As a side note, why are examples of this architecture only ever given as web apis? In talks or in example solutions on github. Its supposed to be frontend agnostic, but people always somehow end up with by far the easiest one.

My particual application is a wpf mvvm one. Mvvm is another architectural style thats supposed to decouple the logic from the UI. Yet i run into the same problems. My viewmodels are in a non wpf project, using an IPopUpService. However this lets my repositories use it too. I dont do it, but i have the option, which seems wrong to me.

The whole "put everything in the applicationlayer via interfaces" thing feels similiar to having just one layer, one big project. I understand a dependency on an IPopUpService isnt the same as a dependency on wpf's PopUpBox, but it feels close enough. Am i thinking wrong somewhere? Is limiting access just not a consideration?

1
  • 2
    I would recommend to remove your "side note" question from the text, don't give folks an easy justification for closing it because you ask two different questions in one.
    – Doc Brown
    Aug 24, 2020 at 20:13

4 Answers 4

2

Normally, you don't put a general "popup" service anywhere, as a popup is a concept purely related to a given implementation of the UI.

To find the right alternative, you have to analyze the concrete case.

  • If you need additional information to do a database query in your persistence layer, the use-case should already deliver this information to the persistence call. Thus, the use case - which normally includes some kind of UI input and output - should already "know" which information is needed, and pass this info to the initial call.
  • If somehow your core domain wants to convey information to an interested party, use some kind of message service. This message may be picked up by the ui to show a popup, this message may also picked up by a logger or an email service, or any number of alternative implementations.

These are only two examples, but I hope they illustrate how you have to shift your way of planning the application away from the question "what happens in the UI" to "what happens in the application".

The whole idea is, that you can use the core domain and to a lesser extent the services in more than a single (UI) context. E.g. the persistence component should be designed so that it can also be used from a batch or from a rest call. This absolutely excludes the ability to ask for additional information in the middle of an operation.

1

You stumbled onto something that is so obvious, most people just don't see it: These architectures don't really decouple anything.

To do what you want in these kinds of architectures, you'll have to export/publish all data related to this popup, and have the UI use this data to display the popup. This is obviously the opposite of decoupling. You need something new on one side you almost always have to modify the other side too.

The way I used to deal with the cognitive dissonance from this, is that I rationalized that I was just publishing some data in the core. It wasn't about the popup, it could be used for something else too. Of course that was rarely if ever the case.

This is of course the reason that all examples of this are web-based and usually very trivial.

So, you've discovered something real. Keep digging, don't trust anybody (there is a lot of bad content out there, some from famous authors) :). Always try things for yourself.

0

Would something like this work?

Something.Ui.WpfApplication
    + Services
        - PopupService

Something.Ui
    + Contracts
        - IPopupService
    + ViewModels

Something.Data
    + Repositories

Something.Core
    + Contracts
    + Domain

This isolates your view objects to your view (presentation) layer.

Core/domain contracts are all the important business operations, but afaik, each layer can have its own contracts too.

Layers higher up implement from layers down below.

Contracts are typically used for interchangeability, so maybe you could end up with something like this

Something.Ui.WpfApplication
    + Services
        - RedPopUpService

Something.Ui.WinFormsApp
    + Services
        - BluePopUpService

Something.Ui
    + Contracts
        - IPopUpService
    + Services
        - PopUpSharedLogic
    + ViewModels
0

"So i create an interface IPopUpService in the application layer. Suddenly i have access to to it in the persistence layer."

The fact that you can reference something doesn't mean that you should; you want to carefully control the number of interdependencies (to minimize complexity; you don't want a tangled web).

Besides, the persistence layer itself essentially implements a number of persistence-related abstractions defined by (and within) the application layer (e.g. one or several interfaces). So you could say pretty much the same thing for those (you can reference them from non-persistence parts of the outer layer - doesn't mean that you should). So there's a decomposition of sorts there, even if it's only implied: layers themselves aren't monolithic. Dependencies going across layers are, generally speaking, actually limited to specific parts of an inner layer, they don't encompass the whole thing.

The application layer will itself be decomposed in some way (by features, use cases, subsystems, whatever...); again, the goal is to control the complexity by minimizing the dependencies. This decomposition can be mostly logical, but you can also at some point decide to divide the layer into multiple DLLs, say. It's still all one layer, but components from an outer layer will only reference the DLLs they need to, not all of them. You shouldn't have to make major design changes to your application to be able to break it down into DLLs like that.

Now, as you develop your system, and gain a better understanding of the problem domain, certain concepts will change, others will emerge, and others will be scrapped. Your IPopUpService is a narrowly specialized interface, conceptually. It's what Robert Martin would call an output port; an abstraction that lets you invoke a lower level service without having to depend on it. This particular abstraction, though, is not all that abstract; this is not, in itself, a bad thing, especially at this stage. But it does mean that the standard assumption is that there's going to be some kind of a GUI involved.

Now, if a class in the persistence layer implemented IPopUpService, that would be rather strange, I agree. Just to clarify something before I continue. You've said:

"Suddenly i have access to [IPopUpService] in the persistence layer."

It's not that you have access to it, rather, you're having a dependency on it, and you're providing an implementation for the application layer to use. The problem is that you've taken a narrowly specialized interface and given it broader purpose without renaming & reconceptualizing it.

If you forget about the GUI aspect for the moment, what IPopUpService actually does is something along these lines: it sends a signal to an outer layer to indicate some business logic–related result (success, failure, etc.). And I do mean "sends a signal", because the application layer is really just calling method on a variable of an abstract type; the side effect (the popup being shown) happens in the presentation layer.

But these signals may have business relevance of their own, so you may want to have them result in more then just a popup being shown. E.g, you may want to make a log of the event, and store it in a file and/or in a database table. So you may end up generalizing this interface (to something like IOperationResultConsumer1) so that it can be used in all of those scenarios, with the presentation, database and infrastructure layers implementing their own versions of it each (one to show a popup, the other two to store an event or a log entry).

Or you may decide, based on other factors and constraints, that it makes more sense to have separate interfaces for these things. E.g., there might be subtle differences in logic that cannot be easily generalized, making the unified interface approach more complicated.

To summarize, my two main points are that (1) the layered view of the system is a very high-level view, and going beyond that, you have to be mindful of how you organize code within the layers, and be deliberate about your dependencies, and (2) the abstractions you initially come up with aren't necessarily the ones you'll end up with - there's going to be (or there should be) a certain amount of refinement over time.


1 Couldn't think of a better name ATM.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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