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
Orderentity 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
Moneyvalue 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
PaymentProcessingServicemight coordinate the interaction betweenOrder,Customer, andPaymentMethodentities.
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, andProductwould be entities. TheInvoiceentity might have a methodcalculate_totalthat sums up the totals of itsInvoiceLinevalue 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:
InvoiceLineandMoneycould be value objects.InvoiceLinerepresents the combination of aProduct, a quantity, and a price (represented as aMoneyvalue 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,
Invoicecould be an aggregate root that ensures the consistency of the entire invoice - for example, it could ensure that there’s at least oneInvoiceLinein anInvoice, and that eachInvoiceLinehas a positive quantity. -
Domain Events: When an
Invoiceis paid, anInvoicePaiddomain 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.
#domain #entities #dataclasses #ddd
Page last modified: 2024-11-13 14:01:29