Business logic¶
In Domain-Driven Design (DDD), business logic primarily resides in three main areas:
-
Entities: Entities encapsulate business rules that are related to the concept that the entity models. For instance, an
Order
entity could have a method to calculate the total price of the order. This is logic that naturally belongs to the order concept in the business domain. -
Value objects: Value objects can also contain business logic, especially when that logic pertains to the value concept that the object represents. For example, a
Money
value object could have a method to convert the amount to a different currency. -
Domain Services: Sometimes, business rules or procedures span multiple entities or value objects, or they don’t naturally fit within a single entity or value object. In such cases, we create a Domain Service to handle this logic. For instance, a
PaymentProcessingService
might coordinate the interaction betweenOrder
,Customer
, andPaymentMethod
entities.
In addition, there are two more places where business logic may reside:
-
Aggregates: An Aggregate is a cluster of associated objects that are treated as a unit for the purpose of data changes. The root of the Aggregate enforces the invariants (business rules) for the entire Aggregate.
-
Domain Events: Domain Events represent something interesting that happened in the domain. They can be used to encapsulate business logic that needs to happen as a result of the event and propagate the impact of the event to other parts of the domain.
While the business logic is mostly present in the areas mentioned above, it’s worth noting that DDD also emphasizes the importance of strategic design and context mapping. This means that the organization of business logic can depend on the specific context and that different models might be appropriate for different parts of the system.
Example: an invoicing system¶
Sure, assuming that InvoiceLine
can’t exist independently of an Invoice
, we can treat it as a Value Object. Let’s modify the example:
- Entities: In this case,
Customer
,Invoice
, andProduct
would be entities. TheInvoice
entity might have a methodcalculate_total
that sums up the totals of itsInvoiceLine
value objects.
class Invoice:
def __init__(self, invoice_lines):
self.invoice_lines = invoice_lines
def calculate_total(self):
return sum(line.total for line in self.invoice_lines)
- Value Objects:
InvoiceLine
andMoney
could be value objects.InvoiceLine
represents the combination of aProduct
, a quantity, and a price (represented as aMoney
value object), and it can calculate its own total.
@dataclasses.dataclass(frozen=True)
class InvoiceLine:
product: Product
quantity: int
price: Money
@property
def total(self):
return self.price * self.quantity
- Domain Services: Suppose we have a business rule that says “when a Customer’s total purchases exceed a certain amount, they are upgraded to a premium status.” This rule spans multiple entities (
Customer
,Invoice
) and thus might be implemented in a domain service.
class CustomerStatusService:
def upgrade_customer_status(self, customer):
total_purchases = sum(invoice.calculate_total()
for invoice in customer.invoices)
if total_purchases > PREMIUM_THRESHOLD:
customer.status = 'premium'
-
Aggregates: In this case,
Invoice
could be an aggregate root that ensures the consistency of the entire invoice - for example, it could ensure that there’s at least oneInvoiceLine
in anInvoice
, and that eachInvoiceLine
has a positive quantity. -
Domain Events: When an
Invoice
is paid, anInvoicePaid
domain event could be triggered, which might kick off other business processes - for example, it could reduce the quantity of the purchased products in stock, or it could update the customer’s total purchases for the status upgrade rule mentioned above.
Remember, these are just examples. The actual design and organization of the business logic would depend on the specific requirements and constraints of your domain.
Page last modified: 2024-09-25 08:35:47