3

I might start by saying that I am aware that usually there's a Repository layer

bookRepository.persist(book)

that abstracts the interaction with the database, but something about using the model directly feels really tempting, as in

book.persist()

This second option would also allow for the model to persist itself when changes are made, which seems pretty useful:

void updateAuthor(string newAuthor){
   this.author=newAuthor
   this.persist()
}

Assuming that it is a non relational database and its persistence logic is as simple as

bookDatabase.update(bookId,book)

Should the model know how to persist itself?

In what situations (if any) would this approach would make sense?

EDIT: Just want to point out that I selected the answer which better described the conditions that had to be met in order for this to make sense. If you're looking for more details on why usually it doesn't, the other replies do a great job on explaining it.

4
  • I'm "o.k." with it, iff the persistence layer is very stable and abstract: e.g. straightforward SQL, JSON, etc. If persistence is either complex, or likely to change in the future, you should split it out. YMMV.
    – user949300
    Sep 12 '20 at 1:02
  • 2
    Whether or not this "should" be done is an opinion. It's certainly not unheard of.
    – Eric King
    Sep 12 '20 at 1:03
  • 1
    "Assuming that [...] persistence logic is as simple asbookDatabase.update(bookId,book)" - sorry, but that statement makes no sense to me. This shows only the persistence API, the logic is what happens inside the update method. A model which "knows" how to persist itself means the logic is implemented in the book class, and not in the bookDatabase class. Or do you have a generic update method in mind, and an book class which has enough meta data to make that work? Even then, the book object would require to hold a reference to the bookDatabase object to make this work.
    – Doc Brown
    Sep 12 '20 at 5:36
  • @DocBrown I was mostly thinking about pymongo and its update function MongoClient(address)["databaseName"]["collectionName"].update({"id":id},book}), given that the whole the MongoClient(address)["databaseName"]["collectionName"]part could be stored staticly as "bookDatabase" Sep 12 '20 at 15:08
3

It depends. There can be scenarios where this is suitable:

  • When you don't implement a larger "enterprise system" or real business software, but something smaller, maybe something like a tool which is required for a single project

  • and this tool is expected to require only one persistence mechanics over its whole lifetime

  • and domain classes like book are not expected to have much more logic than CRUD itself

  • and the persistence mechanics does not hinder creating model objects in-memory, without being attached to a database (for example, for in-memory duplicates, or for tests, or caches and backups)

then creating repositories for each and every model class may be overengineering. But if one of the above conditions is not fulfilled, separating persistence from domain logic becomes pretty quick beneficial.

Let me add that specificially in inhouse development, having to create such kind of tools is not unreasonable. I have seen several sensible scenarios over the years which were not just "prototypes and throwaway scripts", where such a simplistic design was totally sufficient.

2
  • I'm not sure your criteria provide helpful guidance, TBH. They sound a little like "minor dogmas" instead of reasons I can understand and think rationally about. In what scenarios specifically does the active record pattern fall over? Why?
    – svidgen
    Sep 12 '20 at 15:23
  • @svidgen: what I wrote was definitely not meant as some kind of "dogma", not a waterproof checklist one can follow without thinking. It is a rough guideline, something where one will still need experience to see the bigger picture and to reflect about the system as a whole. And as you may have noted, it seems my answer was helpful for the OP, at least.
    – Doc Brown
    Sep 12 '20 at 16:16
6

No, it should not.

Especially these days, a model will have multiple persistence locations (DB, cache, backup) and a few other forms that are useful to be decoupled from the store (human readable, serialized). You certainly don’t want to put all of those into your model, and you shouldn’t change the model when you add a new one or change one (Open Closed Principle). Also, you generally want persistence to be alongside un-persistence, but loading something from a store is awkward/impossible to put in the model in most languages.

Like most guidelines, there are situations where the trade offs don’t make sense. Prototypes and throwaway scripts come to mind, though the standard warning about those things quickly becoming production code applies.

2

Your single question is a mixture of two questions which could be unrolled as follows:

1) A repository is one design pattern, where the action of persisting a book to a database is encapsulated in the method call bookrepo.persist(book).

Is it reasonable to encapsulate the action in a method on the book itself?

To that the answer is: Of course. Why not?

If you take a look at the Active Record pattern, there are such methods on the book object. It is no repository pattern though.

2) Regarding DB access code

Is it reasonable to implement the logic for the database access within the context of the book class?

General answer: No.

As @Telastyn pointed out, the reason to change the book class should not be a change in an underlying peristence thechnology used. So the capability to persist should be delegated.

Additionally: I see a violation of the Single Responsibility Principle. A book should do booky things (like showPrice()). And placing a book onto a shelf is a job for a librarian not for the book itself.

Of course there might be contexts where this orthodox view might be overengineering.


tl;dr

In general it makes a lot of sense separating db access code and the code of your domain object. You end up with at least two objects and for your book object the db access is a dependency which would then be injected.

1

The second approach is a variant of the active record pattern:

  • It's simple: Martin Fowler suggests that it can make sense for simple domains (e.g. no complex object dependencies to cope with) when used in combination with the transaction script pattern (i.e. with a very procedural business logic).

  • But it's limited: due to a lack of separation of concerns (business logic vs. persistence), it quickly proves very inflexible and difficult to maintain. So it is not to be recommended as a first choice in most of the real-life situation.

1

There are (at least) the following options available for a model:

  1. The model could know nothing about persistence
  2. The model could know how to persist
  3. The model could know what to persist
  4. The model could know when to persist

The question's wording might imply that either 1,2,3 come together or none at all, but these are not coupled tightly.

Simple model:

class Book {
   string author;
   updateAuthor(string newauthor) {
     book.author = newauthor;
   }
   getAuthor() {
      return this.author;
   }

}

Model that knows how to persist:

class Book {
   string author;
   updateAuthor(string newauthor) {
     book.author = newauthor;
   }
   persist() {
     FileSystem.openFile("book.txt").write("author=", this.author);
   }
}

Model that knows what to persist:

class Book {
   string author;
   updateAuthor(string newauthor) {
     book.author = newauthor;
   }
   persist(IDataObject output) {
     output.put("author", this.author);
   }
}

Model that knows when to persist:

class Book {
  string author;
  IDataLayer data;
  Book(IDataLayer persistenceLayer) {
    this.data = persistenceLayer;
  }
  updateAuthor(string newauthor) {
    boolean changed = this.author != newauthor;
    this.author = newauthor;
    if (changed) {
      this.data.persist(this);
    }
  }
}

Consider using approach 3, if you want to minimize model's responsibilities while keeping the benefits of flow control.

Model can have none or all knowledge about persistence or anything in between. Give it what it needs, strip everything else, and you will have domain model.

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.