I'm fiddling around with OOP in building simple CRUD systems.
I've decided to focus on using the Repository Pattern for separating Object business logic and Object data persistence (actually saving the object in a persistent data store, i.e a database).
Saving a simple object is straightforward
class Customer {
setData(data) {
this.data = data;
}
}
// PUT Customer
customer = new Customer();
customer.setData(data);
customerRepo.save(customer);
But saving a composite object becomes a bit complicated
But what happens if my Customer class is now includes other objects that also need to be persisted in the DB?
In the following example, setting a customer's data also needs to create an
AuditTrail
which is a set of differences between the previous data and the new data passed in customer.setData()
.
AuditTrail
is a class and in this example it's a classic has-a relationship between Customer
and AuditTrail
class Customer {
setData(data) {
// instantiate an auditTrailCalculator with some constants from the DB
auditTrail = auditTrailRepo.create();
// `calculate()` produces a diff between the previous data and the new data
this.auditTrail = auditTrail.calculate(this.data, data);
}
getAuditTrail() {
return this.auditTrail;
}
}
// PUT customer
customer = Customer();
customer.setId(customerId);
customer.setData(data);
customerRepo.update(customer);
auditTrailRepo.insert(customer.getAuditTrail());
The above example looks clumsy.
- I'm instantiating the
AuditTrail
object using it's repo from within theCustomer
Object. - For performing the whole update of a Customer I clumsily:
- Instantiate a new Customer
- Set it's data
- Get it's auditTrail that was generated inside the object
- Save the AuditTrail to the DB using the
AuditTrailRepo
- Save the Customer to the DB using the
CustomerRepo
My questions:
- Is it correct to instantiate objects from their repo's from within other objects?
- Should I create a factory function instead, which instantiates both Objects,
Customer
&AuditTrail
and return a composition of the 2? - How should I handle saving this composite object?
1 Answer 1
This is clear example of Aggregate as seen in Domain Driven Design.
In this case, the AuditTrail
is part of Customer
's aggregate. And according to DDD, repositories are per-aggregate, not per-entity. So in your case, there would be only CustomerRepository
, which would write an audit every time Customer
is updated.
-
What if
AuditTrail
it wasn't specific to justCustomer
though? I know you requested clarification and I've stated that it is - but i'd like to know what happens in scenarios where it's not.nicholaswmin– nicholaswmin05/04/2017 19:45:55Commented May 4, 2017 at 19:45 -
Also, your answer means that I can only create new
Customer
objects from theCustomerRepository
, otherwise they won't include anAuditTrail
object - which makes this answer obsolete right?nicholaswmin– nicholaswmin05/04/2017 20:20:26Commented May 4, 2017 at 20:20 -
@NicholasKyriakides When I thought about it some more, then if AuditTrail is a Value Object, then there is no problem for it to be part of multiple Aggregates, not only Customer. For the second question, yes, I always considered that creation of new entity should be done through some kind of factory, that is implemented in the persistence layer.Euphoric– Euphoric05/05/2017 06:22:17Commented May 5, 2017 at 6:22
-
Thanks, one last question - In that case,
CustomerRepo.save()
would internally also callAuditTrailRepo.save()
to save the Audit Trail right?nicholaswmin– nicholaswmin05/05/2017 06:30:03Commented May 5, 2017 at 6:30 -
@NicholasKyriakides I would question if there even should be
AuditTrailRepo
. Or at least, it should be hidden in the persistence module.Euphoric– Euphoric05/05/2017 06:31:35Commented May 5, 2017 at 6:31
AuditTrail
specific for Customer entity, or is it used by multiple entities?Customer
entity