18

A typical implementation of a DDD repository doesn't look very OO, for example a save() method:

package com.example.domain;

public class Product {  /* public attributes for brevity */
    public String name;
    public Double price;
}

public interface ProductRepo {
    void save(Product product);
} 

Infrastructure part:

package com.example.infrastructure;
// imports...

public class JdbcProductRepo implements ProductRepo {
    private JdbcTemplate = ...

    public void save(Product product) {
        JdbcTemplate.update("INSERT INTO product (name, price) VALUES (?, ?)", 
            product.name, product.price);
    }
} 

Such an interface expects a Product to be an anemic model, at least with getters.

On the other hand, OOP says a Product object should know how to save itself.

package com.example.domain;

public class Product {
    private String name;
    private Double price;

    void save() {
        // save the product
        // ???
    }
}

The thing is, when the Product knows how to save itself, it means the infstrastructure code is not separated from domain code.

Maybe we can delegate the saving to another object:

package com.example.domain;

public class Product {
    private String name;
    private Double price;

    void save(Storage storage) {
        storage
            .with("name", this.name)
            .with("price", this.price)
            .save();
    }
}

public interface Storage {
    Storage with(String name, Object value);
    void save();
}

Infrastructure part:

package com.example.infrastructure;
// imports...

public class JdbcProductRepo implements ProductRepo {        
    public void save(Product product) {
        product.save(new JdbcStorage());
    }
}

class JdbcStorage implements Storage {
    private final JdbcTemplate = ...
    private final Map<String, Object> attrs = new HashMap<>();

    private final String tableName;

    public JdbcStorage(String tableName) {
        this.tableName = tableName;
    }

    public Storage with(String name, Object value) {
        attrs.put(name, value);
    }
    public void save() {
        JdbcTemplate.update("INSERT INTO " + tableName + " (name, price) VALUES (?, ?)", 
            attrs.get("name"), attrs.get("price"));
    }
}

What is the best approach to achieve this? Is it possible to implement an object-oriented repository?

DFord
  • 1,250
ttulka
  • 373

6 Answers6

8

Very good observations, I completely agree with you on them. Here is a talk of mine (correction: slides only) on exactly this subject: Object-Oriented Domain-Driven Design.

Short answer: no. There should not be an object in your application that is purely technical and has no domain-relevance. That's like implementing the logging framework in an accounting application.

Your Storage interface example is an excellent one, assuming the Storage is then considered some external framework, even if you write it.

Also, save() in an object should only be allowed if that is part of the domain (the "language"). For example I should not be required to explicitly "save" an Account after I call transfer(amount). I should rightly expect that the business function transfer() would persist my transfer.

All in all, I think the ideas of DDD are good ones. Using ubiquitous language, exercising the domain with conversation, bounded contexts, etc. The building blocks however do need a serious overhaul if to be compatible with object-orientation. See the linked deck for details.

7

You wrote

On the other hand, OOP says a Product object should know how to save itself

and in a comment.

... should be responsible for all the operation done with it

This is a common misunderstanding. Product is a domain object, so it should be responsible for the domain operations which involve a single product object, no less, no more - so definitely not for all operations. Usually persistence is not seen as a domain operation. Quite the opposite, in enterprise applications, it is not uncommon trying to achieve persistence ignorance in the domain model (at least to a certain degree), and keeping persistence mechanics in a separate repository class is a popular solution for this. "DDD" is a technique which aims at this kind of applications.

So what could be a sensible domain operation for a Product? This depends actually on the domain context of the application system. If the system is a small one and only supports CRUD operations exclusively, then indeed, a Product may stay quite "anemic" as in your example. For such kind of applications, it may be debatable if putting the database operations into a separate repo class, or using DDD at all, is worth the hassle.

However, as soon as your application supports real business operations, like buying or selling products, keeping them in stock and managing them, or calculating taxes for them, it is quite common you start to discover operations which can be placed sensibly in a Product class. For example, there might be an operation CalcTotalPrice(int noOfItems) which calculates the price for `n items of a certain product when taking volume discounts into account.

So in short, when you design classes, you need to think about your context, in which of Joel Spolsky's five worlds you are, and if the system contains enough domain logic so DDD will be benefitial. If the answer is yes, it is quite unlikely you end up with an anemic model just because you keep the persistence mechanics out of the domain classes.

Doc Brown
  • 218,378
7

Practice trumps theory.

Experience teaches us that Product.Save() leads to lots of problems. To get around those problems we invented the repository pattern.

Sure it breaks the OOP rule of hiding the product data. But it works well.

Its much harder to make a set of consistent rules which cover everything than it is to make some general good rules that have exceptions.

Ewan
  • 83,178
4

DDD meets OOP

It helps to keep in mind that there is not intended to be tension between these two ideas -- value objects, aggregates, repositories are an array of patterns used is what some consider to be OOP done right.

On the other hand, OOP says a Product object should know how to save itself.

Not so. Objects encapsulate their own data structures. Your in memory representation of a Product is responsible for exhibiting product behaviors (whatever they are); but the persistent storage is over there (behind the repository) and has its own work to do.

There does need to be some way to copy data between the in memory representation of the database, and its persisted memento. At the boundary, things tend to get pretty primative.

Fundamentally, write only databases aren't particularly useful, and their in memory equivalents are no more useful than the "persisted" sort. There's no point in putting information into a Product object if you are never going to take that information out. You won't necessarily use "getters" -- you aren't trying to share the product data structure, and you certainly shouldn't be sharing mutable access to the Product's internal representation.

Maybe we can delegate the saving to another object:

That certainly works -- your persistent storage effectively becomes a callback. I would probably make the interface simpler:

interface ProductStorage {
    onProduct(String name, double price);
}

There is going to be coupling between the in memory representation and the storage mechanism, because the information needs to get from here to there (and back again). Changing the information to be shared is going to impact both ends of the conversation. So we might as well make that explicit where we can.

This approach -- passing data via callbacks, played an important role in the development of mocks in TDD.

Note that passing the information to the call back has all of the same restrictions as returning the information from a query -- you shouldn't be passing around mutable copies of your data structures.

This approach is a little bit contrary to what Evans described in the Blue Book, where returning data via a query was the normal way to go about things, and domain objects were specifically designed to avoid mixing in "persistence concerns".

I do understand DDD as an OOP technique and so I want to fully understand that seeming contradiction.

One thing to keep in mind -- The Blue Book was written fifteen years ago, when Java 1.4 roamed the earth. In particular, the book predates Java generics -- we have a lot more techniques available to us now then when Evans was developing his ideas.

VoiceOfUnreason
  • 34,589
  • 2
  • 44
  • 83
2

Maybe we can delegate the saving to another object

Avoid spreading knowledge of fields unnecessarily. The more things that know about an individual field the harder it becomes to add or remove a field:

public class Product {
    private String name;
    private Double price;

    void save(Storage storage) {
        storage.save( toString() );
    }
}

Here the product has no idea if you're saving to a log file or a database or both. Here the save method has no idea if you have 4 or 40 fields. That's loosely coupled. That's a good thing.

Of course this is only one example of how you can achieve this goal. If you don't like building and parsing a string to use as your DTO you can also use a collection. LinkedHashMap is a old favorite of mine since it preserves order and it's toString() looks good in a log file.

However you do it, please don't spread knowledge of fields around. This is a form of coupling that people often ignore until it's to late. I want as few things to statically know how many fields my object has as possible. That way adding a field doesn't involve many edits in many places.

candied_orange
  • 119,268
0

There is an alternative to already mentioned patterns. The Memento pattern is great for encapsulating internal state of a domain object. The memento object represents a snapshot of the domain object public state. The domain object knows how to create this public state from its internal state and vice versa. A repository then works only with the public representation of the state. With that the internal implementation is decoupled from any persistence specifics and it just has to maintain the public contract. Also your domain object has not to expose any getters that indeed would make it a bit anemic.

For more on this topic I recommend the great book: "Patterns, Principles and and Practices of Domain-Driven Design" by Scott Millett and Nick Tune