Replies: 6 comments
-
Can you clarify why you'd expect the lower? From a quick glance, it even looks broken, as there's a cyclic relationship between Payments and Orders. The arrows in the diagram reflect type dependencies. It's also not clear to me why the listening dependency from Payments to Shared should be hidden. Apparently – and it is difficult to judge what's going on without knowing the actual code – the Payments module contains an even listener targeting a type defined in Shared. Same but different for the listens to VS. depends on relationship between Orders and Shared. |
Beta Was this translation helpful? Give feedback.
-
Any chance you can further comment on the questions. I'm inclined to close the ticket otherwise. |
Beta Was this translation helpful? Give feedback.
-
Yes, will do hopefully by the end of the week. |
Beta Was this translation helpful? Give feedback.
-
I would expect that because that's how the business logic works. Currently the listening relation is set to Shared, because the event itself lives in Shared. I'm aware, that since it's a domain event it should live in domain Orders module, but I intentionally put the event in Shared, so that both Orders and Payments module can depend on it and I don't have to duplicate the event code in multiple places - it's a tradeoff that I agree upon.
That's the reason I am setting asynchronous communication between these modules. Payments listens to OrderCompleted event and Orders listen to PaymentCompleted event. Async communication allows to decouple the cyclic relation in the code to relay only on events, not directly on modules.
Currently the the dependency arrow from Payments to Shared doesn't really describe the logic. It is there only, because the event class itself lives in Shared, but it's dispatched in Orders. The "depends on" relation From Payments to Shared makes sense to me, because indeed Payments uses Shared e.g. it imports OrdersComplete event, but it doesn't listen to it, as Shared is not dispatching this event. In other words - the way I envision it is that the listening relationship would be established based on where the event is dispatched instead of where it resides. |
Beta Was this translation helpful? Give feedback.
-
It's always difficult to judge a train of thought if there's no code presented. If
That's contradicting what you stated above (the event type residing in
Then the diagram reflects exactly that decision: you have moved the event to I generally think that the practice of moving code to shared modules needs to be reconsidered, especially for domain events. They're a key part of a module as they signal important state transitions and ultimately form the (logical) API of a module. Moving those to a shared module results in a module with low-cohesion, as the individual elements contained in that one usually do not really relate to each other.
Asynchronous or event-based communication does not resolve cyclic dependencies. If A interacts with B and B interacts with A, they're in a cyclic relationship, independently of whether they talk to each other via events or direct method calls. Again, I think moving the problematic parts into a shared module only masks that fact. Even worse, it hides that the two primary modules semantically depend on each other. A common successful approach in designing such a scenario is defining a leading party, typically from the concept, that semantically depends on the other. While orders without payments might be suboptimal for the business (😬), conceptually, an order can exist without being paid through the system. A payment, however, hardly makes sense without something that is paid. So I guess the implied logical dependency would be |
Beta Was this translation helpful? Give feedback.
-
I would disagree to a certain extent. Although async communication doesn't remove semantic dependency, I wouldn't assume that in a large system containing hundreds of modules (or microservices) there won't be any semantic cyclic dependencies. It that case the whole system would need to be an direct acyclic graph. Even if I would assume this rule on the beginning of the project, the more people work and the bigger the system becomes, at some point probably somewhere a cyclic dependency could appear. Especially, if it was just one event that implies the cyclic dependency, while all the rest of functionalities is quite nicely decoupled and highly cohesive. The most important bit to me is to remove the direct dependencies in the code i.e. importing
with something like this:
I understand that the most natural place to place I am inclined to do this due to the following mental experiment. I know, that my argument is more a philosophical matter, but it explains the reasoning of the original request. I think my point here comes down to this:
That's what actually I would expect:
|
Beta Was this translation helpful? Give feedback.
-
I have 3 domain modules:
To avoid having too many similar/identical DTOs/events in the above modules, I decided to put them in a Shared module of type
ApplicationModule.Type.OPEN
. I've set up async communication between them using@EventListener
/@TransactionalEventListener
.When I generate docs with
Documenter#writeModulesAsPlantUml
I am getting a diagram, where business modules listen to Shared module:while I would expect something like:
As I understand based on the above images, doc generator simply checks in which module an event class resides. To get the desired output, it might check in which module an event class is dispatched instead.
Would that be possible?
Beta Was this translation helpful? Give feedback.
All reactions