I was under the impression that a Domain Model should not have dependencies injected (even repositories).
Yes, that's right.
Should a Domain service have dependencies? I realise that application services have repositories injected.
Here's what Evans wrote
A good SERVICE has three characteristics
- The operation relates to a domain concept that is not a natural part of an entity or value object
- The interface is defined in terms of other elements of the domain model
- The operation is stateless
Statelessness here means that any client can use any instance of a particular SERVICE without regard to the instance's individual history. The execution of a service will use information that is accessible globally, and may even change global information (that is, it may have side effects).
Further along, he addresses your question, although somewhat obliquely
But we are still left with calls to SERVICES in the interbank networks. What's more, in most development systems, it is awkward to make a direct interface between a domain object and external resources. _We can dress up such external SERVICES with a FACADE that takes inputs in terms of the model....
The domain service (facade) would have a dependency on the external service.
That said; looking back 10 years later, I'm not sure that he got the details of this one right. When you interact with state that is distributed somewhere else, that somewhere else may not be available. Distributed communication fails, and if that's not part of your domain, the logic for handling those failures starts to get in the way.
Gary Bernhardt and Cory Benfield both have given very interesting talks about separating side effect concerns from those of the model.
A model that transforms inputs into outputs is simple to reason about; a model that transforms inputs into outputs plus side effects, that's not simple any more.
It's an interesting question that seems to come up in a variety of guises.
I am of the opinion that the best approach is to allow the concept of an object that is a invalid state.
The reason being that the validation rules for an object are usually not set in stone. They may change over time, or be different for different operations. Thus an Object which you created, populated and persisted some time ago, may now be deemed invalid.
If you have setter or constructor validation checks, then you have a big problem in that your application will error when you try to retrieve these entities from your database, or reprocess old inputs etc.
Additionally I don't think that business rules incorporate simple yes/no validation for the most part. If your domain is selling cakes and you don't deliver south of the river, but someone offers you a million pounds to do it. Then you make a special exception.
If you are processing millions of applications and you have strict rules about what characters can be in a field, then you probably have a process for correcting bad fields. You don't want to be unable to accept a bad field at all, it just follows a different path through the Domain.
So, if in the code you are so strict that 'invalid' data can just never exist because the constructor would throw an exception, you are bound to be brittle and fail for questions like "how many people filled the form in wrong?"
Allow the data and fail the operation. This way you can adjust the data or rules for the operation and re run it.
example:
public class Order
{
public string Id {get;set;}
public string Address {get;set;}
public void Deliver()
{
//check address is valid for delivery
if(String.IsNullOrWhiteSpace(this.Address))
{
throw new Exception("Address not supplied");
}
//delivery code
}
}
So here we are unable or don't wish to deliver to blank addresses. The Domain object Order, allows you to populate a blank address but will throw a exception if you attempt to deliver that order.
An application, say a queue worker processing orders from json data stored in a queue, coming across an 'invalid' order:
{
"Address" :""
}
Is able to create the Order object, as there are no validation checks in the constructor, or the setter for Address. However, when the Deliver method is called it will throw the exception and the application will be able to take an action. eg
public class QueueWorker
{
public void ProcessOrder(Order o)
{
try
{
o.Deliver();
}
catch(Exception ex)
{
Logger.Log("unable to deliver order:${o.Id} error:${ex.Message}");
MoveOrderToErrorQueue(o);
}
}
}
The Application can still work with the Invalid Order, moving it to the error queue, accessing its Id, reporting on errors etc. But the Deliver Operation contains the Domain logic of how you want to handle Delivery to blank addresses.
If the Domain logic later changes with further requirements:
public class Order
{
public string Id {get;set;}
public string Address {get;set;}
public void Deliver()
{
//check address is valid for delivery
if(String.IsNullOrWhiteSpace(this.Address))
{
throw new Exception("Address not supplied");
}
if(Address.Contains("UK"))
{
throw new Exception("UK orders not allowed!");
}
//delivery code
}
}
Then you can still process orders on the queue which were generated when UK addresses were allowed and get the expected result.
Its interesting to compare my answer with the one from @VoiceOfUnReason.
I've used a string Address for simplicity and also because there is no absolute definition of what an address is. But if we have a more absolute definition, say his Deposit always has a currency. It's nonsensical to even talk about a deposit with no currency.
In that case yes, you can define a new value type which simply cant exist unless you have specified the currency and loads of potential errors in your code will simply not be possible.
But you have to be sure its a fundamental thing. Otherwise you are asking for trouble later!
Best Answer
IOfferValueCalculator
is not a domain entity. It is a service. So it is perfectly fine for it to be an interface.And there is some nuance in the "Should entities implement interfaces" question. While it is true that creating interface only for it to be implemented by single entity is useless complexity. There is nothing wrong with multiple entities implementing single interface, as those entities might be interchangeably used in code, that runs same logic on all of entities.