Domain-driven design prescribes modelling a particular domain with classes that contain both state and behaviour. Microsoft attempt to support this methodology in modern C# applications through EF Core, which allows constituting graphs of entity objects from a database.
I'm building an application where I model individual configurable business rules as entities. These individual rules belong to a "commission ruleset", and the ruleset arranges these rules into a pipeline that sequentially perform updates on objects passed through that pipeline.
These rules depend on external services to lookup or calculate various things in order to do their job.
What is the best way to allow them to access these services, given they are DDD entities constituted from the database?
Does it make sense to pass through a context object alongside the object being updated which exposes all services that a business rule might require? This feels like it would be an anti-pattern but I can't think of a better alternative.
I'm working on a redesign of the commissions system at my company. This particular domain has a high degree of business complexity and low technical complexity, so I figured DDD was a good fit here.
At our company, we have a number of product consultants (salesmen) that earn commission on orders they sell within a period (a month-long time period). Product consultants are each assigned to a particular commission plan which determines the commission rules that apply to them. A commission rule is a (potentially configurable) business rule that in some way impacts the commission they earn within a period. Commission rules are combined together to form a commission ruleset.
Product consultants should be able to view their statement, which is a snapshot of the commission they've earned so far in the period along with any relevant information that factors into this (orders sold, performance targets met, etc.).
Following Microsoft's recommendations for DDD-based architectural design, we created a number of classes to model the domain which:
- Are mapped to and from the database using EF Core
- Contain both state and relevant business logic (i.e. are not anaemic)
The diagram below shows a simplified view of how these entities look from a persistence point of view:
During statement generation, a set of records in the database are constituted to an object graph with this structure:
A basic statement is generated and then passed to
CommissionRuleset, which sequentially passes the statement to each of the rules that compose it. As well as updating the calculated commission, these rules my potentially affect other parts of the statement that ultimately control how the statement is visually represented to the product consultant:
These rules need to be able access services that calculate their performance, lookup their previous payment records, etc. Right now, I handle this by making all those services available from the statement itself:
In this sense, each commission rule modifies the statement using data it accesses from the statement itself. This doesn't feel like the cleanest solution, and it's quickly turning
Statement into a god class.
One potential alternative would be to somehow inject these services into the individual commission rules themselves, like this:
However, EF Core does not support injecting arbitrary services into entities during construction (yet), and even if it did, I'm not sure if it's good practice to inject domain services into entity constructors.
The only practical remaining alternative I can think of is to pass a "context object" alongside the statement being generated, which would allow the business rules to get the data they need. So far all the external calculations/lookups these business rules require relate to the product consultant the statement is being generated for (performance calculations or records, payment records, etc.), so I could create at least create a nominally cohesive aggregate service (or context object) along the lines of
IProductConsultantDataService or something.
Is this the best approach or is there a better alternative to this?
Sidenote: there are some parts of my redesign that I feel iffy about, but the decision to model business rules as separate classes that can be arranged in a pipeline feels incredibly "right" for this particular system.
There are many business rules in this system, and new ones are likely to come along in the future. New rules can be encapsulated as their own individual classes, added to the pipeline independently, removed independently, tested in isolation; arbitrary sets of rules with arbitrary configurations can be arranged in arbitrary orders within a ruleset, and using EF Core this all cleanly persists and reconstitutes from the database.
I'm very happy with this particular aspect of the design.