2

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 the Customer 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?
  • Is AuditTrail specific for Customer entity, or is it used by multiple entities? – Euphoric May 4 '17 at 19:28
  • It's specific for Customer entity – Nik Kyriakides May 4 '17 at 19:34
3

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 just Customer 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. – Nik Kyriakides May 4 '17 at 19:45
  • Also, your answer means that I can only create new Customer objects from the CustomerRepository, otherwise they won't include an AuditTrail object - which makes this answer obsolete right? – Nik Kyriakides May 4 '17 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 May 5 '17 at 6:22
  • Thanks, one last question - In that case, CustomerRepo.save() would internally also call AuditTrailRepo.save() to save the Audit Trail right? – Nik Kyriakides May 5 '17 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 May 5 '17 at 6:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.