How to accomplish Persistance Ignorance in DDD?

2019-06-10 14:39发布

问题:

I am working on the persistence layer of a project that involves Workspace's, each of which may contain zero, one, or more Document's. (I am trying to follow Domain-Driven-Design principles but my questions may not be directly related to this.)

Question 1: Should I separate out persistence? I.e., do you design you entity and value classes in such a way that you can

  • Create entities and values in memory, just as you would do without persistence (possibly using a Factory method Workspaces.newWorkspace(...)), and
  • Call a separate persist() method (possibly in a Repository) to take care of persistence?

Or should my factory method Workspaces.newWorkspace() create a persisted entity (which will be persisted once the transaction closes)?

If the answer to this question is "Separation, dude!" then I wonder how to accomplish this in an elegant way. My first approach was (in Scala pseudocode):

class Workspace(title: String, documents: List[Document], id: Option[Long]) {
  def add(d: Document) =  // ...
  def remove(d: Document) = // ...
}

However, if a workspace can have many documents, this is not good (limited by RAM). My next approach, following "How not to inject services into entities", was this:

class Workspace(title: String, docSupplier: DocSupplier, id: Option[Long]) {
  def add(d: Document) = docSupplier.add(d)
  def remove(d: Document) = docSupplier.remove(d)
}

With this, the workspace factory can create new workspaces like this:

class Workspaces {
  def newWorkspace(title: String) = new Workspace(title,
    // A supplier that maintains a simple `List[Document]`
    new DocSupplier() {
      def add(d: Document) = list.add(d)
      def remove(d: Document) = list.remove(d)
    }, id)
}

Also, my repository can reconstruct workspaces it fetches from the database like this:

class WorkspaceRepository {
  def findById(id: Long) = // ... calls `createDoc()`

  def createDoc(...) = new Workspace(title,
    // A supplier that remembers changes so they can be persisted in `persist()`
    new DocSupplier() {
      def add(d: Document) = changes.rememberAdd(d)
      def remove(d: Document) = changes.rememberRemove(d)
    }, id)
}

Question 2: Is this the way to do this?! Puh, it's a lot of code, with a lot of boilerplate!

回答1:

Should I separate out persistence?

Yes, just the way that you describe.

Or should my factory method Workspaces.newWorkspace() create a persisted entity (which will be persisted once the transaction closes)?

No, because persisting a transient entity should be an explicit operation, such as when you add a new Workspace. The factory handles the creation of the object instance and the repository handles persistence. As indicated by pabrantes the Unit of Work pattern can be used in conjunction with repositories.

However, if a workspace can have many documents, this is not good (limited by RAM).

This is a common scenario in DDD - while reaching for persistence ignorance you have to consider technical constraints. The first thing to consider is whether the Workspace entity needs to reference the collection of Document instances at all. Are there invariants that Workspace needs to enforce? Are there transactional boundaries in place? Object references are only one of the ways of representing relationships. Another way is with a repository. So instead of having a Document collection on the Workspace class, you provide a repository method which allows retrieval of documents associated with a specific workspace. Given that the number of documents may be large, the repository can support paging and filtering as well.

Also take a look at Effective Aggregate Design by Vaughn Vernon for in depth treatment of these issues.