I've read a lot and I still can't make up my mind about which is the better approach with regard to Domain Validation in DDD.
We have 2 major ambitions:
Ambition 1
Model should never ever end up in an invalid state. Its state should be changed strictly through its methods which, in turn, would throw exceptions if passed invalid arguments.
What I like about this approach is that you end up with clean, guaranteed states possible.
What I don't like is that all model calling code ends up with explosion of exceptions handling which doesn't sound right.
Ambition 2
Abandon ambition 1, let models end up in invalid state but in the code that uses the model call model.IsValid() and if false do model.GetErrors() and go from there.
This approach kills a ton of exceptions but its not that nice that you have to check explicitly for errors.
I just can't make my mind yet. I hope someone might offer some convincing argument for any of approaches here to help me make my mind.
It depends on your context and I think that there is a reasonable cause to use both patterns. Sometimes a state that looks 'invalid' is actually valid in a given context, and the ability to determine the validity is a meaningful domain operation.
A pattern we've adopted in a recent system is to have two separate contexts:
input collector & validator
meaningful business function
In the first context, the data is populated onto the entity as submitted, and entity contains the business rules of determining validity - but the validity becomes a state of the entity. This allows the API consumer to submit data, and retrieve the resource associated with the entity, including meaningful representations of valid or invalid statuses and reasons.
If the entity is valid, then a key business rule in this context is that it permits a state transition that causes (via a domain event) a command to be sent to the second context.
In the second context, invalid states are not permitted, and are enforced by throwing exceptions.
The value of this pattern is that in the first context, we collect data on the types of invalid data submitted by users and give the api consumer persistent access to the history of their operations including the records captured around what was invalid and why.
But in the second context, which contains more meaningful business logic, the business logic can be kept quite clean and clear because once the entity is successfully constructed, the code can assume a valid state.
DDD advocates for the Ambition 1, so an entity or value object can never be in an invalid state. What you don't like about this is that you have to handle the exceptions. There is many ways of handling exceptions properly. You can either let the exception propagate and catch it in a higher level or create your main Domain exceptions so it can be easier to handle. But as a general rule if something went wrong (exception) it should be taken care of.
For the "Ambition 2" I don't think it is a good idea since everywhere you will have to deal with an object you need first to check if it is valid or not cause you can never guarantee that. And I this can easily become much more complicated than catching exceptions. Think about aggregates you need to make sure every object in the aggregate is valid.
For data validation, you almost always want to have a very clear boundary between "here's a representation of something, but I don't know if its any good", and "here's a representation of something that satisfies all of the constraints for use".
Once a piece of immutable data has been checked, it should be tagged so that all of the consumers know that it is good. The usual mechanism for tagging is to rely on the type system. Money.amount is represented in memory as an immutable integer, but we also have the guarantee that it is non negative (or whatever).
An alternative to having a factory return a object that you have to check for validity, is to have it return an object that captures the fact that the object may not have been valid.
Try<Money> money = MoneyTree.showMeTheMoney(-7)
money, in this example, is either going to be a Success<Money>, if the MoneyTree was able to do something useful with -7, or it's going to be a Failure.
In 2014, Maurício Linhares wrote a summary of the idea. Google will lead you to others.
The tactical patterns in the Blue Book were drawn from what were understood to be best practices in Java circa 2003. In particular, Evans explicitly calls out the fact that the coding patterns should allows the developers to work with the domain, rather than the plumbing. Exceptions tend to be a better choice for that so that the domain semantics aren't lost behind a bunch of error handling.
For domain logic, on the other hand, exceptions are a poor choice; if the business problem that you are modeling has multiple paths; then the code structure should make those paths explicit -- using exceptions to model control flow is usually a bad idea, and domain driven design does not include any constraints that would require them.
Related
I've just run into a problem while trying to re-design our existing business objects to a adopt a more domain-driven design approach.
Currently, I have a Product Return aggregate root, which handles data related to a return for a specific product. As part of this aggregate, a date needs to be provided to say what month (and year) the return is currently for.
Each product return MUST be sequential, and so each product return must be the next month after the previous. Attempting to create a product return that doesn't follow this pattern should result in an exception.
I had thought about passing along a Domain Service to the method (or constructor) that sets the PeriodDate for the return, but I'm at a loss for how I would do this. Even if the domain service had a reference to a repository, I can't see it being appropriate to put a "GetNextReturnDate()" on that repository.
For background, each product return is associated with a product. I was reluctant to make the product the aggregate root, as loading up all the product returns just to add one seemed like an extremely non-performant way of doing things (considering this library is going to be used with a RESTful Web API).
Can anyone provide suggestions as to how I should model this? Is it a matter of just changing the aggregate root and dealing with the performance? Is there some place in the Domain that 'query' type services can be placed?
As an example, the current constructor the for product return looks like this:
public ProductReturn(int productID, int estimateTypeID, IProductService productService)
{
// This doesn't feel right, and I'm not sure how to implement it...
_periodDate = productService.GetNextReturnDate(productID);
// Other initialization code here...
}
The IProductService (and it's implementation) sit in the Domain layer, so there's no ability to call SQL directly from there (and I feel that's not what I should be doing here anyway)
Again, in all likelihood I've modelled this terribly, or I've missed something when designing the aggregate, so any help would be appreciated!
I Think my broader problem here is understanding how to implement constraints (be it foreign, unique, etc.) inside of a domain entity, without fetching an entire list of returns via a domain service, when a simple SQL query would give me the information required
EDIT: I did see this answer to another question: https://stackoverflow.com/a/48202644/9303178, which suggests having 'Domain Query' interfaces in the domain, which sound like they could return the sort of data I'm looking for.
However, I'm still worried I'm missing something here with my design, so again, I'm all open to suggestions.
EDIT 2: In response to VoiceOfUnreason's answer below, I figured I'd clarify a few things RE the PeriodDate property.
The rules on it are as follows:
CANNOT be NULL
It MUST be in sequence with other product returns, and it cannot be in a valid state without this fulfilled
It's a tricky one. I can't rely on the date passed in because it could then very well be out of sequence, but I can't figure out the date without the service being injected. I am going to transform the constructor into a method on a Factory to remove the 'constructor doing work' anti pattern.
I may be overly defensive over the way the code is currently, but it feels the amount of times the ReturnService has to be injected is wrong. Mind you, there are lots of scenarios where the return value must be recalculated, but it feels as though it would be easy to do this just before a save (but I couldn't think of a clean way to do this).
Overall I just feel this class has a bit of a smell to it (with the injected services and whatnot), but I may be needlessly worrying.
I had thought about passing along a Domain Service to the method (or constructor) that sets the PeriodDate for the return, but I'm at a loss for how I would do this.
I strongly suspect that passing a domain service to the method is the right approach to take.
A way of thinking about this: fundamentally, the aggregate root is a bag of cached data, and methods for changing the contents of the bag of data. During any given function call, it's entire knowledge of the world is that bag, plus the arguments that have been passed to the method.
So if you want to tell the aggregate something that it doesn't already know -- data that isn't currently in the bag -- then you have to pass that data as an argument.
That in turn comes in two forms; if you can know without looking in the aggregate bag what data to pass in, you just pass it as an argument. If you need some of the information hidden in the aggregate bag, then you pass a domain service, and let the aggregate (which has access to the contents of the bag), pass in the necessary data.
public ProductReturn(int productID, int estimateTypeID, IProductService productService)
{
// This doesn't feel right, and I'm not sure how to implement it...
_periodDate = productService.GetNextReturnDate(productID);
// Other initialization code here...
}
This spelling is a little bit odd; constructor does work is usually an anti-pattern, and it's a bit weird to pass in a service to compute a value, when you could just compute the value and pass it in.
If you feel like it's part of the business logic to decide how to compute _periodDate, which is to say if you think the rules for choosing the periodDate belong to the ProductReturn, then you would normally use a method on the object to encapsulate those rules. On the other hand, if periodDate is really decided outside of this aggregate (like productID is, in your example), then just pass in the right answer.
One idea that may be crossing you up: time is not something that exists in the aggregate bag. Time is an input; if the business rules need to know the current time to do some work, then you will pass that time to the aggregate as an argument (again, either as data, or as a domain service).
The user can’t pass a date in, because at any given time, the date for a return can ONLY be the next date from the last return.
Typically, you have a layer sitting between the user and the domain model -- the application; it's the application that decides what arguments to pass to the domain model. For instance, it would normally be the application that is passing the "current time" to the domain model.
On the other hand, if the "date of the last return" is something owned by the domain model, then it probably makes more sense to pass a domain service along.
I should also mention - a return is invalid without a date, so I can’t construct the entity, then hope that the method is called at a later time
Are you sure? Effectively, you are introducing an ordering constraint on the domain model - none of these messages is permitted unless that one has been received first, which means you've got a race condition. See Udi Dahan's Race Conditions Don't Exist
More generally, an entity is valid or in valid based on whether or not it is going to be able to satisfy the post conditions of its methods, you can loosen the constraints during construction if the post conditions are broader.
Scott Wlaschin's Domain Modeling Made Functional describes this in detail; in summary, _periodDate might be an Or Type, and interacting with it has explicit choices: do this thing if valid, do this other thing if invalid.
The idea that constructing a ProductReturn requires a valid _periodDate isn't wrong; but there are tradeoffs to consider that will vary depending on the context you are operating in.
Finally, if any date is saved to the database that ISN’T the next sequential date, the calculation of subsequent returns will fail as we require a sequence to do the calculation properly.
If you have strong constraints between data stored here and data stored somewhere else, then this could indicate a modeling problem. Make sure you understand the implications of Set Validation before you get too deeply invested into a design.
Your problem is querying across multiple aggregates(product returns) for decision making(creating a new product return aggregate).
Decision making based on querying across aggregates using a repository will always be wrong; we will never be able to guarantee consistency, as the state read from the repository will always be a little old.(Aggregates are transaction boundaries. The state read from repository will only be correct at that very instant. In the next instant the aggregate's state may change.)
In your domain what I will do is create a ProductReturnManager AggregateRoot which manages returns for a particular product and a ProductReturn Aggregate which specify one specific return of the product. ProductReturnManager AggregateRoot manages the lifecycle of ProductReturnAggregate to ensure consistency.
The logic for assigning a next month sequential date to ProductReturn is in ProductReturnManager (basically ProductReturnManager act as a constructor). The behavior of product return will be in ProductReturnAggregate.
The ProductReturnManager can be modeled as a Saga, which is created on first CreateProductReturnCommand (for a productId), and same saga is loaded for further CreateProductReturn commands(correlated by productId). It handles ProductReturnCreatedEvent to update its state. Saga creation logic will be according to your business rules(Eg. saga creation is done on InvoiceRaisedForProduct event and handles CreateProductReturn commands.)
sample code:
ProductReturnManagerSagaState{
ProductId productId;
//can cache details about last product return
ProductReturnDetails lastProductReturnDetails;
}
ProductReturnManagerSaga : Saga<ProductReturnManagerSagaState>,IAmStartedByMessages<CreateProductReturn>{
Handle(CreateProductReturn message){
//calculate next product return date
Date productReturnDate = getNextReturnDate(Data.lastProductReturnDetails.productReturnDate);
//create product return
ProductReturnAggregateService.createProductReturn(Data.productId, productReturnDate);
}
Handle(ProductReturnCreatedEvent message){
//logic for updating last product return details in saga state
}
}
ProductReturnAggregate{
ProductId productId;
Date productReturnDate;
ProductPayment productPayment;
ProductReturnState productReturnState;
//commands for product return
markProductReturnAsProcessing();
}
This is an excellent video by Udi Dahan on working across multiple aggregates.
Question: what is the best, efficient and future proof way to
rehydrate an aggregate from a repository? What are the pro's and con's of the provided ways and are my perceptions correct?
Let's say we have an Aggregate Root with private setters but public getters for accessing state
Behaviour is done through methods on the aggregate root.
A repository is instructed to load an aggregate.
At the moment I see a couple of possible ways to achieve this:
set the state through reflection (manual or automatic eg.
Automapper)
make constructors that accept properties so state is set
load the aggregate with a state object
1) Jimmy Bogard alludes that his tool Automapper isn't meant for two-way mapping. But some people argue that we have to be pragmatic, use tools in a way it helps you.
For me, I don't like a full rehydration through reflection. Maybe Automapper ceise to exist or aggregate roots are bent in such a way the mapping can be done (see some comments of Vaughn on his article).
2) creating constructors for rehydration, with a couple of parameters so the state of the aggregate is rehydrated in a correct way.
These couple of parameters can expand (= new constructors) or the definition can change. I like this approach, except the part of having a bunch of parameters.
3) the state is a property of the aggregate root. The state is encapsulated in a new object and this object is build by the repository and is then given to the aggregate root for proper init.
Some people argue that building this state object is more work (new class, exposure of state properties on entity and aggregate root to enforce business rules), but it provides a clean way to initiliaze the state.
Say that we need event sourcing, does the loading of a state resemble in loading events? And does the state object provide a way of handling events? Is it more future proof?
I would argue that trying to future-proof too much represents a trap that many people fall into that adds undue complexity to a codebase. There is a fine balancing act between sound architectural decisions and over-architecting a solution to problem that is not guaranteed to exist.
That being said, I fully agree with what Jimmy says, in regards to AutoMapper not being intended for two-way mapping. Your domain represents the "truth" in your application, and should not be directly mutable. I have worked on projects with two-way mappings, and while they do work, there is a tendency to start treating the domain objects as nothing more than DTOs. It becomes painful when you start having read-only properties, having to reflect to do your setting - tooling or not. From a DDD perspective, we should not be allowing for outside influences to simply say what a property value should be, because it will lead to an anemic domain model over time, most likely.
Internal states do work well, but they are at the cost of additional overhead and complexity. There is a legitimate trade-off, as you mention, in that you are adding a fair amount of work. However, you can use that opportunity to allow the aggregate to validate the state against the self-contained business rules within the aggregate, prior to allowing the state to be set. That addresses the largest concern that I have with two-way mapping. You can at least enforce that a state object contains valid data and then only construct the aggregate if it is valid. It is more testable, as well. The largest problem that I have seen with this approach is that the skill level of your team will have a direct bearing on the success of this being utilized correctly. It could be argued that the complexity does not add enough value to implement domain-wide, as you will likely have aggregates that have different levels of churn. A couple of projects that I have been involved in have used this approach, and I found little advantage over straight constructor usage.
Normally, I use constructors for rehydration in most cases. It walks the line between not being overly-complex, plus it leaves responsibility for the aggregate to allow or disallow the construction of the object - again, allowing for the domain to be in control of whether the hydration attempt would result in a valid object. A good compromise to constructor bloat is the use a mutable DTO as a parameter for the constructor, essentially acting as a data structure to maintain a consistent constructor signature over time. In that essence, it is also somewhat future-proof. It takes the most attractive perk of the state object approach, which is the clean signatures, but removes the additional layer of an internal abstraction.
You mention event sourcing as a possibility down the road. State loading is not very similar to what you would be doing, at all (in my opinion). With a state object, you are snapshotting the state of the aggregate at a given point in time. With event sourcing, you will be replaying events, each of which represents the data required to mutate the state, as opposed to the state, itself. As such, your constructor will likely be a collection of events, representing a chain of deltas to mutate the state repeatedly, until it reaches the current state. When you want to hydrate your aggregate, you will supply it with the events that are related to that aggregate, and it will replay them to get to the current state. This is one of the true strengths of event sourcing, as well. You are forcing the hydration of your domain objects to go through the business logic required to create them, each time. Given a list of events, the aggregate will enforce that each state change is valid by applying the event in a consistent fashion, whether the event is being applied in real-time, or replayed to get to the current state.
Back to the future-proof aspect, as it relates to event sourcing, there is a conscious effort required when events require change. Since you have to replay an event to get to the current state, you will very likely have to deprecate events and bring up new events to transition to as your business logic changes. You may (read as "likely will") find yourself versioning events. Not only does your aggregate need to understand current state change requirements, but it also needs to understand previous state change requirements. So, if you change an event handler, you will have to ensure that it will be valid for existing events, as well. When you are adding additional data to an event, it is usually not too involved. But when you start removing data from an event signature, you instantly make that event at risk for being incompatible with earlier structures. Likewise, even changing the names of the data structures inside of an event can cause backwards compatibility issues. If you start event sourcing, you do not need to worry as much about future-proofing as you do backwards compatibility. Event sourcing is great, but be prepared for additional complexity.
I am just getting my feet wet with my first Domain-driven Design project. The project's domain model involves a bounded context that deals with collision detection of moving geometries located in a three-dimensional scene.
A central part of the business logic would be the collision detection algorithm aka the CollisionDetector. The algorithm must be configured with some parameters that influence its behaviour and performance. So it has some state attached making me think of it as an entity or rather an aggregate in the first place. Note that there is no requirement to persist this entity to the DB - all its parameter values are transient. Also note that there is only a single CollisionDetector instance present in the domain, so implementing an in-memory repository giving access to that single aggregate seems a bit overdone to me.
Summing things up we have a stateful, transient entity with a singleton nature.
So here is my question: While "normal" aggregates in DDD should always be accessed via repositories, how should I deal with such a unique entity instance?
Is it advisable to use (evil) Singleton pattern here?
Should I rather register that entity with my IOC?
Should I implement an in-memory repository to just hold that single instance?
Is it even reasonable to model it as an entity at all? (It doesn't need identity for it's unique.)
Should I refactor to make it a (stateful!) domain service instead?
Is it maybe a design smell for the domain model to have several aggregates of this type?
I've heard about Sagas. Could they be the solution to my problem?
Please let me now if you need more context to help me with this issue.
P.S. And excuse my broken english. I'm not a native speaker.
Edit:
I feel I should give some further detail on CollisionDetector class and the state attached to it.
Maybe calling it transient was misleading. My intention was to point out that CollisionDetector will never be persisted to a storage. Its entire life is spent in active memory. Creation, modification and deletion are all part of its life cycle, persistence is not.
Another important quality of CollisionDetector is what I refer to as its singleton nature meaning that CollisionDetector is instanciated only once (but not necessarily using the GoF Singleton pattern). I am not sure whether the concept of identity is applicable to a singleton instance (at least there is no need for identification as there is just one), which is the only thing that keeps me from making CollisionDetector an entity/aggregate.
So let me shed some light on CollisionDetector's state as requested by #plalx, #guillaume31 and #arootbeer.
As mentioned already, there is a simple set of scalar parameter values used to tweak the algorithm to be either more accurate or faster. These parameters can be adjusted by the user at any time to fit his needs.
The CollisionDetector keeps track of the geometries and their pose inside the scene. This is achieved via Domain Events: When one of the geometries changes its pose an event handler will automatically trigger a collision test. Likewise newly detected collisions are published by the CollisionDetector via Domain Events as well. Note that not all geometries are considered to be collision candidates, the user may exclude some of them from the test. Hence the CollisionDetector holds a blacklist of geometry ids (beeing part of its state).
In order to detect collisions in realtime, the algorithm requires a preprocessing which is done in a separate initialization routine of CollisionDetector. Whenever a geometry is added to or removed from the scene, the preprocessing becomes stale and needs to be refreshed. The CollisionDetector is aware of that i.e. it knows whether a re-initialization is required or not.
Hope this helps!
Rethinking my question I must admit that it's merely academic. A pragmatic solution could be to make CollisionDetector an aggregate and just register it with the IoC container. What do you think?
The algorithm must be configured with some parameters that influence its behaviour and performance. So it has some state attached making me think of it as an entity or rather an aggregate in the first place. (emphasis mine)
I don't consider the configuration of the CollisionDetector to be its state. It is only configuration and cannot change once the CollisionDetector is instantiated, so it is something much simpler than mutable state that you typically find in entities.
As a consequence, I clearly consider the CollisionDetector a domain service. This means you should handle it as follows:
Register the CollisionDetector service in IoC. The configuration of the CollisionDetector is probably considered domain logic, so it could make sense to instantiate the CollisionDetector in a CollisionDetectorFactory. If that's the approach you take, register the factory in IoC instead. In any case, make sure the clients of the CollisionDetector don't have to know about its configuration.
By the way, don't use singletons (i.e. the GoF singleton pattern) when you work with IoC. You can always register a service as single instance, which has the same effect as the singleton pattern, but avoids its problems.
We all heard that injecting repository into aggregate is a bad idea, but almost no one tells why.
I will try to write here all disadvantages of doing this, so we can measure rightness of this statement.
First thing that comes into my head is Single Responsibility Principle.
It's true that by injecting repository into AR we are violating SRP, because retrieving and persisting of aggregate is not responsibility of aggregate itself. But it says only about "aggregate itself", not about other aggregates. So does it apply for retrieving from repository aggregates referenced by id? And what about storing them?
I used to think that aggregate shouldn't even know that there is some sort of persistence in system, because it doesn't have to exist. Aggregates can be created just for one procedure call and then get rid of.
Now when I think of it, it's not right, because aggregate root is an entity, and entity has sense only if it has some unique identity. So why would we need unique identity if not for persisting? Even if it's just a persistence in a memory. Maybe for comparing, but in my opinion it's not a main reason behind the identity.
Ok, let's assume that we retrieve and store OTHER aggregates from inside of our aggregate using injected repositories. What are other consequences beside SRP violation?
For sure there is a problem with having no control over persisting of aggregates and retrieving is some kind of lazy loading, which is bad for the same reason (no control).
Because of no control we can come into situation when we persist the same aggregate few times, where it could be persisted only once, or the same aggregate is loaded one hundred times where it could be loaded once, hence performance is worse. Also there might be problem with stale data.
These reasons practically disqualifies ability to inject repository into aggregate.
Here comes my main question - why can we inject repositories into domain service then?
Not the same reasons applies here? It's just like moving logic out of aggregate into separate function and pretend it to be something different.
To be honest, when I stared to write this SO question, I had no good answer for that. But after hours of investigating this problem and writing of this question I came to solution. Rubber duck debugging.
I'll post this question anyway for others having the same problems. Of course with my answer below.
Here are the places where I'd recommend to fetch aggregates (i.e. call Repository.Get...()), in preference order :
Application Service
Domain Service
Aggregate
We don't want Aggregates to fetch other Aggregates most of the time, because this blurs the lines, giving them orchestration powers which normally belong to the Application layer. You also raise the risk of the Aggregate trespassing its jurisdiction by modifying other Aggregates, which can result in contention and performance problems, not to mention that transactions become more difficult to analyze and the code base to reason about.
Domain Services are IMO a good place to fetch Aggregates when determining which aggregates to modify is domain logic per se. In your game example (which might not be the ideal context for DDD by the way), which units are affected by another unit's attack might be considered domain logic, thus you may not want to place it at the Application Service level. This rarely happens in my experience though.
Finally, Application Services are the default place where I call Repository.Get(...) for uniformity's sake and because this is the natural place to get a hold of the actors of the use case (usually only one Aggregate per transaction) and orchestrate calls to them.
That doesn't mean Aggregates should never be injected Repositories, there are exceptions, but other alternatives are almost always better.
So as I wrote in a question, I've found my answer already in the process of writing that question.
The best way to show this is by example:
When we have a simple (superficially) behavior like unit attacking other unit, we can write something like that.
unit.attack_unit(other_unit)
Problem is that, to attack an unit, we have to calculate damage and to do that we need another aggregates, like weapon and armor, which are referenced by id inside of unit. Since we cannot inject repository inside of aggregate, then we have to move that attack_unit logic into domain service, because we can inject repository there. Now where is the difference between injecting it into domain service, and not into unit aggregate.
Answer is - there is no difference. All consequences I described in question won't bite us. In both cases we will load both units once, attacking unit weapon once and armor of unit being attacked once. Also there won't be stale data, even if we mutate weapon object during process and store it, because that weapon is retrieved and stored in one place.
Problem shows up in different example.
Lets create an use case where unit can attack all other units in game in one process.
Problem lies in how we implement it. If we will use already defined unit.attack_unit and we will call it on all units in game (iterating over them), then weapon that is used to compute damage will be retrieved from unit aggregate, number of times equal to count of units in game! But it could be retrieved only once!
It doesn't matter if unit.attack_unit will be method of unit aggregate, or if it will be domain service unit_attack_unit. It will be still the same, weapon will be loaded too many times. To fix that we simply have to change implementation and with that probably interface too.
Now at least we have an answer to question "does moving logic from aggregate method to domain service (because we want to access repository there) fixes problem?". No, it does not change a thing.
Injecting repositories into domain service can be as dangerous as injecting it into aggregate if used wrong.
This answers my SO question, but we still don't have solution to real problem.
What can we do if we have two use cases: one where unit attacks one other unit, and second where unit attacks all other units, without duplicating domain logic.
One way is to put all needed aggregates as parameters to our aggregate method.
unit.attack_unit(unit, weapon, armor)
But what if we will need like five or more aggregates there? It's not a good way. Also application logic will have to know that all these aggregates are needed for an attack, which is knowledge leak. When attack_unit implementation will change we would also might to update interface of that method. What is the purpose of encapsulation then?
So, if we can't access repository to get needed aggregate, how can we smuggle it then?
We can get rid of idea with referencing aggregates by ids, or pass all needed aggregates from application layer (which means knowledge leak).
Or maybe reason of these problems is bad modelling?
Attacking of other unit is indeed an unit responsibility, but is damage calculation its responsibility? Of course not.
Maybe we need another object, like value object MeleeAttack(weapon, armor), yet when we add more properties that can change result of an attack, like enchantments on unit, it gets more complicated.
Also I think that we are now creating objects based on performance, not our on domain.
So from domain driven design, we get performance driven design. Is that what we want? I don't think so.
"So why would we need unique identity if not for persisting?" - think of an account scenario, where several John Smiths exist in your system. Imagine John Smith and John Smith Jr (who didn't enter the Jr in signup) both live at the same address. How do you tell them apart? Imagine I'm trying to write a recommendation engine based upon their past purchases . . . .
Identity is a quality of equality in DDD. If you don't have an identity unique from your fields, then you're a ValueObject.
What are consequences of using repository inside of aggregate vs inside of domain service?
There's a reasonably strong argument that you shouldn't do either.
Riddle: when does an aggregate need to see the state of another aggregate?
The responsibility of an aggregate is to control change. Any command that would change the state of the domain model is dispatched to the aggregate root responsible for the integrity of the state in question. By definition, all of the state required to ensure that the command is currently permitted is contained within the aggregate boundary.
So there is never any need to peek at the data outside of the aggregate when making a change to the model.
In which case, you don't ever need to load another aggregate, which makes the "where" question moot.
Two clarifications:
Queries will often combine the state of multiple aggregates, and will often need to follow a reference from one aggregate to another. The principle above is satisfied because queries treat the domain model as read-only. You need the state to answer the query, but you don't need the invariant enforcement because you aren't changing anything.
Another case is when you need state from another aggregate to process a command properly, but small latency in the data is an acceptable risk to the data. In that case, you query the "other" aggregate to get state. If you were to run that query within the domain model itself, the right way to do so would be via a domain service.
In most cases, though, you'll be equally well served to run the query when generating the command (ie, in the client), or when handling the command (in the application, outside the domain). It would be very unusual for a business to consider domain service latency to be acceptable but client latency to be unacceptable.
(Disconnected clients are one case where that can be especially problematic; when the command is generated and then queued for a long period of time before being dispatched to the server).
DDD teaches us to build our classes like their real-world prototypes.
So instead of using setters
job = new Job
job.person = person
job.since = time.Now()
job.title = title
we define well-named methods in our aggregation root
job = person.promote(title, /** since=time.Now() **/)
Now the tricky part
Assume we have an UI for an HR where he/she enters a new title via the HTML form and makes a typo like "prgrammer" (Of course in real application there'd be a select list, but here we have a text input), or selects a wrong date (like default today)
Now we have a problem. There are no typos in real world. Our John Doe is definitely a "programmer" and never a "prgrammer"
How do we fix this typo in our domain model?
Our Person has just promote, demote, fire, etc. methods, which reflect the HR domain model.
We could cheat a little bit and change the Job record directly, but now we have a Job.setTitle method, that doesn't reflect our domain model and also, setters are evil, you know.
That may look a little "academic", but that really bugs me when I try to build a good domain model for a complex application
Another side of DDD is invariants - "always valid" entity. And when you try to break this invariant (some rule) you must stop execution and say "loudly" adout this (throw exception). So, you need to have a list of valid titles and when you try to change title (does not matter how) to invalid state, you must throw some usefull exception.
To "fix" typo situations you must separate operations in your domain promote is one operation (it may check something, sent contratulation email :) and so on). And edit operation - just to edit some properties. So, the differenece is in logic of operations. You can't call promote without some preconditions (for example, required experience of worker), but you can call edit and fix worker's name because of type.
And usually this operations are separated between different users: only HR's can promote but a worker can edit his name, if it's wrong.
This solution is very complicated for such example, but it's always with DDD.
The main concept - separate operations. Each one with their own conditions, permissions, rules.
A question about invariants (rules).
If a client is purely entering data, then the underlying domain in this (bounded) context is not very deep. In these cases, it's fine to use a CRUD style application and allow titles to be changed (setTitle()).
Just make sure dependent BCs (e.g., billing, vacation planning, ...) where no such thing as "invalid data" exists, can react to changes in your CRUD context appropriately.
The application should enforce input correctness before it reaches the domain layer, no garbage input. If that means using a dropdown for the job titles then so be it. You can validate the title against existing titles.
In my company of 18 thousand employees, typo happens all the time. You are going to have to be pragmatic about this and accept that there will be setters in your code (one way or another)
Pragmatic thinking is very much at the core of the domain driven design, and is what keep things simple.
"Purity is good in theory, but in practice it can be very difficult to achieve, and sometimes you must choose the pragmatic approach" - Patterns, Principles, and Practices of Domain-Driven Design (2015)
"There are no typos in real world", I get what you mean, but that's not true, there are human mistakes in real world scenarios and they should be accounted for in your domain if they are frequent.
If data entry errors aren't frequent it may not be worth the extra modeling efforts and those could perhaps just get fixed directly in the DB. It also depends if the business wishes to learn something about those mistakes or not.
However, if data entry errors are frequent, it might be an indicator that the system is perhaps not offering enough guidance and the business may wish to learn more about those errors in order to make processes more efficient and less error-prone.
You may wish to implement an operation such as job.correctTitle(...), perhaps in a BC dedicated to data corrections? Also, it's probably very rare that each and every piece of information will be erroneous so corrective operations can be segregated. That means you probably do not need a job.correctAllInformation(...) kind of operation.
This whole scenario is very fictive since job titles would usually be managed in a separate BC from where they are used and they would probably get picked from a list, therefore typos would be less frequent, but you will always have to deal with data entry errors. Choosing the appropriate solution is not always easy and will vary from case to case, but try to stay pragmatic and not strive for the perfect model in every sphere of your domain.