Reputation: 129
I'm trying to follow some of the more current design principles including SOLID and Domain Driven Design. My question is around how people handle "Initializing" Domain Objects.
Here's a simple example:
Based on SOLID, I should not depend on concretions, so I create an interface and a class. Since I'm taking advantage of Domain Driven Design, I create an object with relevant methods. (i.e. not anemic).
Interface IBookstoreBook
{
string Isbn {get; set;}
int Inventory {get; set;}
void AddToInventory(int numBooks);
void RemoveFromInventory(int numBooks);
}
public class BookstoreBook : IBookstoreBook
{
public string Isbn {get; set;}
public int Inventory {get; private set;}
public void AddToInventory(int numBooks);
public void RemoveFromInventory(int numBooks);
}
To help with testing and be more loosely coupled, I also use an IoC container to create this book. So when the book is created it is always created empty. But, if a book doesn't have an ISBN and Inventory it is invalid.
BookstoreBook(string bookISBN, int bookInventory) {..} // Does not exist
I could have 4 or 5 different classes that use a BookstoreBook. For one,
public class Bookstore : IBookstore
{
...
public bool NeedToIncreaseInventory(BookstoreBook book) { ...}
...
}
How does any method know is getting a valid book? My solutions below seem to violate the "Tell Don't Ask" principle.
a) Should each method that uses a Bookstore book test for validity? (i.e. should NeedToIncreaseInventory test for a books validity? I'm not sure it should have to know what makes a BookstoreBook valid.)
b) Should I have a "CreateBook" on the IBookstoreBook object and just "assume" that clients know they have to call this anytime they want to initialize a BookstoreBook? That way, NeedToIncreaseInventory would just trust that "CreateBook" was already called on BookstoreBook.
I'm interested in what the recommended appreach is here.
Upvotes: 0
Views: 867
Reputation: 14064
Based on SOLID, I should not depend on concretions
If you're referring to the Dependency Inversion principle, it does not exactly say that.
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
No domain entity is of a higher level than another and normally no object in the Domain layer is a "detail", so DIP usually doesn't apply to domain entities.
I also use an IoC container to create this book
Considering that BookstoreBook
has no dependency, I'm not sure why you would do that.
How does any method know is getting a valid book?
By assuming that the book is Always Valid, always consistent. This usually requires having a single Book constructor that checks all relevant rules at creation time, and state-changing methods that enforce invariants about the Book.
a) ...
b) ...
You're mixing up two concerns here - making sure that Book
is in a consistent state wherever it is used, and initializing a Book
. I'm not sure what your question is really about in the end, but if you apply the "always valid" approach and forget about Book being an interface/higher level abstraction, you should be good to go.
Upvotes: 0
Reputation: 101150
First of all, a great way to make sure that entity state only can be set by behavior (methods) so to make all property setters private. It also allows you to make sure that all related properties are set when the state changes.
But, if a book doesn't have an ISBN and Inventory it is invalid.
There you have two business rules. Let's start with ISBN. If a book is not valid without it it HAVE to be specified in the constructor. Otherwise it's fully possible to create a book which is invalid. An ISBN also have a specified format (at least the length). So that format have to be validated too.
Regarding the inventory I believe that it's not true. You might have books that are sold out or books that can be booked before their release. Right? So a book CAN exist without an inventory, it's just not likely. If you look at the relation between inventory and books from the domain perspective they are two separate entities with different responsibilities.
A book is representing something that the user can read about and use that information to decide whether it should be rented or purchased.
An inventory is used to make sure that your application can fulfill your customers request. Typically it can be done by a delivery directly (decrease the inventory) or by a backorder (order more copies from your supplier and then deliver the book).
Thus the inventory part of the application do not really need to know everything there is to know about the book. Thus I would recommend that the inventory only knows about the book identity (that's typically how root aggregates can reference each other according to Martin Fowler's book).
An inversion of control container is typically used to to manage services (in DDD the application services and the domain services). It's job is not to act as a factory for domain entities. It will only complicate things without any benefit.
Upvotes: 1
Reputation: 57267
To help with testing and be more loosely coupled, I also use an IoC container to create this book.
Why is your IoC container creating books? That's a bit strange. Your domain model should by container agnostic (wiring together the interfaces and the implementation is the concern of your composition root).
How does any method know is getting a valid book?
The domain model knows it is getting a valid book, because it says so right there in the interface.
The data model knows it is producing a valid book, because the constructor/factory method accepted its arguments without throwing an exception.
Should each method that uses a Bookstore book test for validity?
No, once you have a Book, it is going to stay valid (there shouldn't be any verbs defined in your domain model that would create an invalid data model).
Should I have a "CreateBook" on the IBookstoreBook object and just "assume" that clients know they have to call this anytime they want to initialize a BookstoreBook? That way, NeedToIncreaseInventory would just trust that "CreateBook" was already called on BookstoreBook.
It's normal to have a factory for creating objects. See Evans, chapter 6.
books can be created from a database and many other places. I'm assuming others have had to solve this issue if they are using DDD and I am wondering about their approach. Should we all be using factories - as you suggest that take the needed data as input?
There are really only two sources for data -- your own book of record (in which case, you load the data via a repository), and everywhere else (where you need to make sure that the data conforms to the assumptions of your model.
Upvotes: 0
Reputation: 387
First off, I think your BookstoreBook
doesn't have any really relevant methods, which means it doesn't have any relevant behavior, no business rules at all. And since it doesn't contain any business rules it actually is anemic. It just has a bunch of Getters and Setters. I would argue that having a method like AddToInventory
that ends up just adding +1 to a property is no meaningful behavior.
Also, why would your BookstoreBook
know how many of its type are in your Bookstore
? I feel like this is probably something the Bookstore
itself should keep track of.
As for point a): no, if you're creating books from user input you should check the provided data before you even create a new book. That prevents you from ever having invalid books in your system.
As for the creation of the object, the question is will you ever have more than one book type? If the answer is no you can drop the interface and just instantiate a book in a class that is responsible for creating new books from user input for example. If you need more book types an abstract factory may be useful.
Upvotes: 2