16

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,240
  • 2
  • 15
  • 30
ttulka
  • 353
  • 3
  • 13
  • 6
    _OOP says a Product object should know how to save itself_ - I'm not sure that's correct really... OOP in itself doesn't really dictate that, it's more of a design/pattern problem (which is where DDD/whatever-you-use comes in) – jleach Feb 09 '19 at 10:15
  • OOP says the object is an _active_ entity and should be responsible for all the operation done with it. Integrity of objects should not be violated and this is exactly what getters/setters do. And the traditional implementation of a repository requires them... At least according to my understanding of OOP :-) – ttulka Feb 09 '19 at 10:34
  • 1
    Remember that in the context of OOP, it's talking about objects. Just objects, not data persistence. Your statement indicates that a object's state should not be managed outside itself, which I agree with. A repository is responsible for loading/saving from some persistence layer (which is outside of the realm of OOP). The class properties and methods should maintain their own integrity, yes, but this doesn't mean another object cannot be responsible for persisting the state. And, getters and setters are to ensure integrity of incoming/outgoing data of the object. – jleach Feb 09 '19 at 10:39
  • 1
    "this doesn't mean another object cannot be responsible for persisting the state." - I didn't say that. The important statement is, that an object should be *active*. It means the object (and no one else) can delegate this operation to another object, but not the other way around: no object should just collect information from a passive object to process its own selfish operation (as a repo would do with getters). I tried to implement this approach in snippets above. – ttulka Feb 09 '19 at 10:52
  • If, metaphorically, the object was a person, she can write her ideas down by herself or tell someone to do it for her. Getters are like allowing someone to insert wires into her brain to read her ideas directly without knowing what will actually happen with them. – ttulka Feb 09 '19 at 10:53
  • _Getters are like allowing someone to insert wires into her brain to read her ideas directly_ - I'm sorry, but I completely disagree. Getters would be like asking her thoughts on something, and she would then sort out how to respond, and do so. In any case, I think it's clear we're not on the same page here... I'll sign off and let someone else handle the question. Best of luck – jleach Feb 09 '19 at 10:56
  • 1
    @jleach You're right, our undestanding of OOP is different, for me getters+setters are no OOP at all, otherwise my question had no sense. Thank you anyway! :-) – ttulka Feb 09 '19 at 11:00
  • 1
    Here is an article about my point: https://www.martinfowler.com/bliki/AnemicDomainModel.html I'm not againt the anemic model in all cases, for example it is a good strategy for functional programming. Just not OOP. – ttulka Feb 09 '19 at 11:03
  • A getter is an OOP construct specifically designed to allow the object to control what information it can give out. A setter is an OOP construct specifically designed to allow the object to validate and constrain the data that can come in. This is the gateway that enforces that an object's data isn't open to everything else. Stating that getters and setters are no OOP at all seems like an awfully bold statement. I fear you are akin to looking at advanced courses (I'm familiar with Martin's work), while having fundamental misunderstandings about 101 level courses. – jleach Feb 09 '19 at 11:07
  • 1
    I am still working on my fundamental misunderstandings... Anyway, my base definition of an object is a bunch of behavior. It means no data. What are getters/setters others than data? For me it's more a construct specifically designed to allow working with objects in procedural manner. I really see no difference between `String getName()`/`void setName(String name)` and `public String name`... it makes an object to a poor data structure. – ttulka Feb 09 '19 at 11:15
  • It seems to be more of us https://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html – ttulka Feb 09 '19 at 11:25
  • 1
    To give a name to the second option you describe, this is the [Active record pattern](https://en.wikipedia.org/wiki/Active_record_pattern). **It's not bad**, it's just another tool available to you. I get a sense that you're looking for the correct way to design an OOP system, and the unfortunate truth is that there is no single correct way. Your job as a software engineer is to make trade-offs and balance speed of delivery with maintainability, and both options you describe could be the best way in different contexts. – Vincent Savard Feb 09 '19 at 15:48
  • _"there is no single correct way"_ sad, but sounds right to me. – ttulka Feb 09 '19 at 16:10
  • Active record... are you sure? I understand this patter as using inheritance to get the ability of persistence from a parent class. It would be like `class Product extents ActiveRecord` and then `product.save()`. That would bring tight coupling into the domain space, which is what I wanted to avoid with my `Storage` interface. – ttulka Feb 09 '19 at 16:15
  • 2
    @ttulka I just wanted to reaffirm, that there are at least a couple of us thinking the same way. "An object is a bunch of behavior", and not data. Be aware,that this is different from how most people code, even in oo languages. Following this idea to the fullest often results in radically different designs from what we are used to seeing. However, as anecdotal evidence I can bring my last 5 years, where I was "allowed" to introduce my own designs, that this thinking does indeed result in much more maintainable software. – Robert Bräutigam Feb 09 '19 at 19:29
  • Persistence is a domain boundary, so the points I've spoken about [in a question about displaying data](https://softwareengineering.stackexchange.com/a/340796/193669) to the user apply here too. Getters are not bad per se, they're bad when misused. That's a huge difference. – Andy Feb 10 '19 at 12:23
  • @Andy +1 for your answer there - you're definitely right, that persistence and displaying (UI) is the same concert from this point of view. In my example code above I was considering to introduce an interface `Medium` to unify both of them into a one method (then I found it too confusing for example purposes). Of course, the message name can't be `save`, but rather `serialize` or something like this. Segregation of concerns into two models via domain events (reactive programming) is another interesting approach, which could make sense in special use-cases. Thanks a lot for your input! – ttulka Feb 10 '19 at 14:10

6 Answers6

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
  • 199,015
  • 33
  • 367
  • 565
  • Your point sounds very sensible to me. So, the product becomes an anemic data structure when crossing a border of a context of anemic data structures (database) and the repository is a gateway. But this still means I have to provide access to object's internal structure via getter and setters, which then become a part of its API and could be easily misused by other code, that has nothing to do with persistence. Is there a good practice how to avoid this? Thank you! – ttulka Feb 09 '19 at 12:21
  • *"But this still means I have to provide access to object's internal structure via getter and setters"* - unlikely. The internal state of a persistance-ignorant domain object is usually given exclusively by a set of domain-related attributes. For these attributes, getters and setters (or a constructor-initialization) must exist, otherwise no "interesting" domain operation would be possible. In several frameworks, there are also persistence features available which allow to persist private attributes by reflection, so the encapsulation is only broken for this mechanism, not for "other code". – Doc Brown Feb 09 '19 at 17:19
  • 1
    I agree that persistence is usually not part of domain operations, however it should be part of the "real" domain operations inside the object that needs it. For example `Account.transfer(amount)` should persist the transfer. How it does that is the object's responsibility, not some external entity's. Displaying the object on the other hand *is* usually a domain operation! The requirements usually describe in great detail how stuff should look. It is part of the language among project members, business or otherwise. – Robert Bräutigam Feb 09 '19 at 19:08
  • @RobertBräutigam: the classic `Account.transfer` for usually involves two account objects, and a unit of work object. The transactional persisting operation could then be part of the latter (togther with calls to related repos), so it stays out of the "transfer" method. That way, `Account` can stay persistence-ignorant. I am not saying this is necessarily better than your supposed solution, but yours is also just one of several possible approaches. – Doc Brown Feb 10 '19 at 14:16
  • @DocBrown I don't see how an Account can be persistence-ignorant, but object-oriented at the same time. (Referring here to the original question.) Do you have some gitrepo you consider good examples? If the object is persistence-ignorant, but should be somehow persisted, this implies some other object has access to its internals, does it not? – Robert Bräutigam Feb 10 '19 at 16:32
  • @RobertBräutigam: see my first comment above. And yes, for making persistence possible, encapsulation may have to be weakened to some degree, that's true for every "external" or generalized persistence mechanism, not just in context of DDD or Accounts. You can say "this is not OO any more", but IMHO that's a very single edged, orthodox point of view on OO, not a pragmatic one. – Doc Brown Feb 10 '19 at 21:20
  • 1
    @RobertBräutigam Pretty sure you’re thinking too much about the relation between the object and the table. Think about the object as having a state for itself, all in memory. After doing the transfers in your account objects, you’d be left with objects with new state. That’s what you’d like to persist, and fortunately the account objects provide a way to let you know about their state. That doesn’t mean their state has to be equal to the tables in the database - i.e the amount transferred could be a money object containing the raw amount and the currency. – Steve Chamaillard Feb 14 '19 at 19:56
7

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.

Robert Bräutigam
  • 11,473
  • 1
  • 17
  • 36
  • Is your talk somewhere to watch? (I see are only slides under the link). Thanks! – ttulka Feb 09 '19 at 21:26
  • I only have a German recording of the talk, here: https://javadevguy.wordpress.com/2018/11/26/video-german-object-oriented-domain-driven-design-talk/ – Robert Bräutigam Feb 09 '19 at 22:16
  • Great talk! (Fortunately I speak German). I think your whole blog is worth reading... Thank you for your work! – ttulka Feb 10 '19 at 09:47
  • Very insightful slider Robert. I found it very illustrative but I got the feeling that at the end, many of the solutions addressed to not breaking encapsulation and LoD are based on giving a lot of responsabilities to the domain object: printing, serialization, UI formatting, etc. Doesn't that increase coupling between the domain and the technical (implementation details)? For example, the AccountNumber coupled with Apache Wicket API. Or Account with whatever Json object is? Do you think it's a coupling worth having? – Laiv Feb 10 '19 at 14:58
  • @Laiv The grammar of your question suggests that there is something wrong with using technology to implement business functions? Let's put it this way: It's not the coupling between domain and technology that's the problem, it's coupling between different abstraction levels. For example `AccountNumber` *should* know that it can be represented as a `TextField`. If others (like a "View") would know this, *that* is coupling that should not exist, because that component would need to know what `AccountNumber` consists of, i.e. the internals. – Robert Bräutigam Feb 10 '19 at 16:44
  • There's nothing wrong per se. I use to say that we must decide how much coupling is ok, because it's going to be always coupling somewhere. The hard decision is *where I place the coupling*. Maybe I could have find a better way to make the question. – Laiv Feb 10 '19 at 18:20
6

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
  • 70,664
  • 5
  • 76
  • 161
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
  • 32,131
  • 2
  • 42
  • 79
  • 2
    Also worth to mention: "save itself" would always require interaction with other objects (either a file system object, or a database, or a remote web service, some of these might in addition require a session to be established for access control). So such an object would not be self-standing and independent. OOP can therefore not require this, since its intent is to encapsulate object and reduce coupling. – Christophe Feb 09 '19 at 14:06
  • Thank you for a great answer. First, I designed the `Storage` interface in the same way you did, then I considered high coupling and changed it. But you're right, there is an unavoidable coupling anyway, so why not to make it more explicit. – ttulka Feb 09 '19 at 14:31
  • 1
    _"This approach is a little bit contrary to what Evans described in the Blue Book"_ - so there is some tension after all :-) That was actually the point of my question, I do understand DDD as an OOP technique and so I want to fully understand that seeming contradiction. – ttulka Feb 09 '19 at 14:39
  • 1
    In my experience, each of these things (OOP in general, DDD, TDD, pick-your-acronym) all sound nice and fine in and of themselves, but whenever it comes to "real-world" implementation, there's always some tradeoff or less-than-idealism that must be for it to work. – jleach Feb 09 '19 at 15:14
  • I disagree with the notion the persistence (and presentation) are somehow "special". They are not. They should be part of the modeling to the extend the requirements demand. There does not need to be an artificial (data-based) boundary *inside* the application, unless there are actual requirements to the contrary. – Robert Bräutigam Feb 09 '19 at 19:02
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
  • 102,279
  • 24
  • 197
  • 315
  • This is in fact the code I posted in my question, right? I used a `Map`, you propose a `String` or a `List`. But, as @VoiceOfUnreason mentioned in his answer, the coupling is still there, just not explicit. It's still unnecessary to know the product's data structure to save it both in a database or a log file, at least when read back as an object. – ttulka Feb 09 '19 at 14:46
  • I changed the save method but otherwise yes it's much the same. The difference is the coupling is no longer static allowing new fields to be added without forcing a code change to the storage system. That makes the storage system reusable on many different products. It just forces you to do things that feal a little unnatural like turning a double into a string and back into a double. But that can be worked around as well if it's really an issue. – candied_orange Feb 09 '19 at 14:48
  • See Josh Bloch's [hetrogenous collection](http://www.informit.com/articles/article.aspx?p=2861454&seqNum=8) – candied_orange Feb 09 '19 at 14:54
  • But as I said, I see the coupling still there (by parsing), only as not being static (explicit) brings the disadvantage being unable to be checked by a compiler and so more error-prone. The `Storage` is a part of the domain (as well as the repository interface is) and makes such a persistence API. When changed, it's better to inform clients in compilation time, because they have to react anyway not to get broken in runtime. – ttulka Feb 09 '19 at 14:55
  • That's a misconception. The compiler can't check a log file or a DB. All it's checking is if one code file is consistent with another code file that also isn't guaranteed to be consistent with the log file or DB. – candied_orange Feb 09 '19 at 15:01
  • Sure, there is no 100% guarantee, but when the product attributes change, the infrastructure implementation (db or log) will get informed in time of compilation, wich I understand as a benefit. – ttulka Feb 09 '19 at 15:08
  • We it's not. It's complexity for the sake of complexity. The Product is the single source of truth for static knowledge of fields. Also please [don't use doubles for money](https://softwareengineering.stackexchange.com/a/344327/131624). – candied_orange Feb 09 '19 at 15:25
  • @candied_orange I'm not sure I get where you stand. You are also against Repositories then that "take" data out of the object? Since that also spreads knowledge of fields. You are also against "Views" that are outside of the object but know the fields of the object? – Robert Bräutigam Feb 09 '19 at 19:36
  • @RobertBräutigam I'm against anything that statically spreads knowledge that a private field exists being called either OO, encapsulated, or private. Carving up an object and spreading its core around isn't OO. It works. It's popular. But it isn't OO. Make decisions in one place. Views are no exception. Views and models are supposed to have an insulating layer of presentation logic between them. – candied_orange Feb 09 '19 at 20:16
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

Roman Weis
  • 224
  • 1
  • 1