We recently reviewed our two year old DDD / Hexagonal architecture based online shop ASP.NET MVC application and tried to identify the DDD related issues which should be refactored (or at least avoid in new applications). Here is a short abstract about the list of the main findings:
Domain Anaemia
Some of the domain objects are quite anaemic. That is mainly the case in places where we are the application is integrating web services which are already covering most (or all) of the domain stuff.
An example for this is a pricing web service which takes care about customer price calculations, rebates, currency conversions and so on (in other words: a lot of business rules). Our conclusion is that it in fact makes sense to apply DDD to this pricing service – but it does not make sense to apply DDD in the parts of the consuming applications that are dealing with this topic because all you do is get the data from the pricing service, pass it through the separate layers and display it. No business rules and stuff, so no need for a domain layer.
We probably would nowadays even go a step further: we would actually prefer a pure non-DDD presentation application which is only taking care about presentation concerns and is integrating web services that are containing all the business rules and are DDD based. Like this it can be avoided to have kind of a hybrid architecture in one application (parts of the application are worth applying DDD, other parts are just passing results from the infrastructure layer to the UI). Moreover in the past it turned out that most of the “DDD worthy” stuff will sooner or later be used by other clients than our application (e.g. the prices are needed by the companies CRM system) and then it anyway makes sense to separate this parts from the application.
Other examples than the pricing service which I already mentioned would be a product service, a cart management service, an order tracking service or an availability service.
Separation of application services
We initially thought that it would make sense to separate our different application services (the “use case controllers”) from each other, e.g. we have an independent “CartApplicationService”, “CatalogApplicationService”, “PricingApplicationService” and so on.
Now we had the following situation more than once: one of these services actually had to use data that was already processed by one of the other services. Because we had defined that as being not possible (we used Visual Studio’s Architecture Diagram functionality to forbid it) the developers usually found workarounds containing duplicated code (sometimes only few lines, sometimes more) which is clearly violating the DRY principle.
This strict separation does not make sense. The reason for this is that there are basically 3 classifications of services (chapter 6 of the book "SOA in Practise" covers the details):
- basic services wrap or hide implementation details of a specific single backend and can be either data-driven or logic-driven
- composed services are using ("orchestrate") multiple basic services or other composed services
- process services handle long running workflows or business processes and are - in contrast to the other classifications - usually stateful.
Only the first of these - the basic service - does have the constraint that it must not communicate with other services. Though the author mainly talks about the classification of web services the same can be applied to application services. The important thing is to be aware of which type an application service is and deal with its dependencies accordingly. This approach is much better than finding workarounds including acceptance of DRY-violations.
Application service return types
Application services are sometimes called “use case controllers”. Might be due to this we made the mistake that we created specialized return data types (DTOs) for more or less every application service method. That certainly resulted in an explosion of the number of DTO classes and classes that are mapping domain objects to these DTOs. On the other hand, view models in our presentation layer were more or less useless. They were nearly always a 1:1 representation of the DTOs.
The preferred way is to let the application services return much fewer different data types (usually the DTO representations of the domain objects) and use view models as they are thought to be used, i.e. mapping to formats that are needed by the UI.
DAOs versus repositories
At the point of time we started modelling the application we were not aware of the differences between repositories and DAOs at all. We for instance created two assemblies within the infrastructure layer – one for database access and one for service proxies. When we started using real repositories (instead of DAOs that were named repositories) we soon needed repository implementations that contained both database access components and service proxies. Now we had the choice between creating a new third assembly containing these repositories and referencing the two others or to arbitrarily choose one of the existing assemblies for putting the repository implementations there and referencing the other assembly.
All of these solutions seem to be suboptimal – the solution is to just create one infrastructure assembly containing everything (which does not mean that every type of component should have access to all other types – which in turn can be avoided introducing Visual Studio Architecture Diagrams or using a tool like NDepend).
No comments:
Post a Comment