I've been very busy with trying to understand the concepts of ddd and Model layer lately. Read tons of articles, examples, Q and A's, spent many hours on it. And still I'm not sure if I got some principles right.
One of them is the answer to the question: How much business logic should exist in Domain Objects? Some sources say Domain Objects should be attached with the whole business logic, on the other hand, I came across articles where I assumed it should be as tiny, as possible and only represent its values. It makes me really confused.
In my understanding, domain objects are classes, that represent entities in the domain.
So, lets for example, go with Invoice entity. Each invoice consist of its items. To compute invoice value, we must sum all items values (it's very simple example, in the real world there would be cases like adding tax, computing paid value, etc)
class Invoice
{
public $id;
public $items = [];
public $status;
const STATUS_PAID = 'paid';
const STATUS_NOT_PAID = 'not_paid';
public function isPaid()
{
return $this->status == self::STATUS_PAID;
}
public function getInvoiceValue()
{
$sum = 0;
foreach($this->items as $item) {
$sum += $item->value;
}
return $sum;
}
}
In my understanding, method isPaid() is in the right place. It refers to its own data. But I'm not sure about getInvoiceValue(). We operate here on other domain objects.
Maybe we should use domain objects just to represent data only, but use some decorators to perform more advanced tasks?
Thanks in advance.
All invariant to use-cases logic (business entities, business workflow components, e.g. Domain model, Domain services) goes to the Domain layer (Domain logic). This layer is responsible for concepts of the business domain and business rules.
Value objects are not only containers of data - they can also contain business logic. The fact that the value objects are also immutable makes the business operations both thread-safe and side-effect free.
Domain logic (aka business logic, business rules, and domain knowledge) is the logic that makes business-critical decisions. All other types of logic orchestrate the decisions made by the domain model and transform them into side-effects: save them to the data store, show to the user, or pass to 3rd-party services.
All invariant to use-cases logic (business entities, business workflow components, e.g. Domain model, Domain services) goes to the Domain layer (Domain logic). This layer is responsible for concepts of the business domain and business rules. The Infrastructure layer may have IoC, Cache, Repositories, ORM, Cryptography, Logging, Search engine, etc.
This layer doesn’t contain any business logic, does not hold the state of business objects, can keep the state of an application task’s progress. All invariant to use-cases logic (business entities, business workflow components, e.g. Domain model, Domain services) goes to the Domain layer (Domain logic).
As you might already know, the best choice to implement DDD in PHP is the Doctrine ORM. In order to implement aggregates and repositories, we need to make some changes in our entities and create some files in our domain layer. Today I've decided to implement a small part of the application in which the user can create a page or modify it.
Domain-Driven Design (also known as DDD) is a software development methodology for designing complex projects and deliver the final software product, so it meets the businesses goals. DDD helps developers focusing on the evolution of the project using a base model.
I'm not sure if there's a right answer for these kind of questions, because applying DDD really depends on the particular domain you're applying it to. There are places where your implementation could be perfectly valid, if it satisfies the business needs. In others, like you mentioned with taxes and the like, it wouldn't. So I'd say that you need to keep asking questions about your domain to fully understand what your needs are before translating them into code.
Having said that, if you have a more complex scenario that requires some extra knowledge of the external world to come up with the value of an invoice, one option would be to represent that in your domain explicitly. In your example, that could be an InvoiceProducer, which could have a signature like:
class InvoiceProducer {
public function __construct(TaxProvider $taxProvider) {
$this->taxProvider = $taxProvider;
}
public function invoiceFor(array $items) {
new Invoice($items, $this->calculateValue($items));
}
private function calculateValue(array $items) {
$sum = array_reduce($items, function($acc, $item){
$acc += $item->value;
}
return $this->taxProvider->applyTaxTo($sum);
}
}
Another option would be to use some sort of Strategy pattern, which would leave your implementation very similar to the way it is now, but you'd pass with your call the way you want the taxation to be calculated:
public function getInvoiceValue(TaxProvider $taxProvider)
{
$sum = 0;
foreach($this->items as $item) {
$sum += $item->value;
}
return $taxProvider->applyTaxFor($sum);
}
Again, it really depends on how your specific domain works but, as you can see, the implementation shouldn't be that big of a deal. Is more about how it all fits within your domain.
How much business logic should exist in Domain Objects? [...] I came across articles where I assumed it should be as tiny as possible and only represent its values.
Beware of the Anemic Domain Model that almost exclusively consists of data and lacks behavior. DDD is about creating a behavior-rich domain model. Thus it's fine to add logic into domain classes.
DDD emphasizes good object oriented design, putting methods and data together, thereby promoting highly cohesive systems.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With