2

The biggest difficulty I am having is finding responsibilities of each object identified by me in the system or so called problem space. I am positing a very simplified description of a book reader.

This time I thought like which actions can be performed on an object and that action became the method on that object, later who is going to perform those action became the parameter of the message/method but I am not sure because in that way every message would have something as a parameter. I would like to constraint the discussion to this level so that I could get clear discussion and answers.

/*
A user should be able to search a book from a library
bookmark it, favorite it or rate it.

A Book
  1. Can be favorited  (by whom?)
  2. Can be bookmarked (by whom?)
  3. Can be rated      (by whom?)
  4. Can be added to a library?
  5. Can be removed from a library?

A Library
  1. Can be Searched
  2. Can be modified?
    a. Add book
    b. Remove book

Relationship:

A library can have many book (o2m)
A library can have many users and an user can subscribe to many libraries(m2m)
A user can rent many books (o2m) needs a new model (rent)?
*/

interface Book {
  void bookmark(User user);
  void favorite(User user);
  void rate(User user);
}

interface Library {
  List<Book> search();
  int add();
  boolean remove();
  List<Book> all();
}

public class Main {
  public static void main(String... args) {
    System.out.println("Starting application");
  }
}

This is the high level view and I would like to know if this approach is correct?

CodeYogi
  • 2,166
  • If that is your biggest difficulty, then I would say you are pretty fortunate. –  Sep 28 '17 at 16:31
  • Why would a book care who bookmarks, favorites or rates it? – Dunk Sep 29 '17 at 17:53
  • @Dunk I don't know that's why I asked the question. – CodeYogi Sep 29 '17 at 18:06
  • @CodeYogi - I suggest you read up on use-cases. They'll help identify classes and responsibilities. e.g. The user selects a book. The user adds the selected book to their favorites. This allows you to identify user, book AND favorites as good potential classes. User is basically your UI. It needs to provide a means to initiate selecting a book and adding it to favorites. Book needs to be uniquely identified and possibly be selectable. Favorites will just be a collection of books. The "hidden" class that coordinates the processing is the use-case. I tend to call them operations. – Dunk Sep 29 '17 at 18:34

3 Answers3

3

There is no one right way — there are multiple ways that would not be wrong. Especially when you have many-to-many relationships, like favorites that relate books to users.

When it comes to identifying responsibilities, I'll often consider — at the domain level, in terms of domain concepts and relationship — who owns the information/knowledge, which describes, preserves, and needs to be modified. It is the owner of that state who has the responsibility to hold, hand out, and update. So, I'd look to essential state to help identify responsibilities and their ownership.

Looking toward the domain and model for the most natural domain concepts: to me, it would be more natural that a user owns their list of of favorites, rather than a book owning a list of users who have favorite'd. So, in OOP, I'd model action (fav'ing and unfav'ing) as user object actions rather than book object actions.

However, you may find your application has the need to not just traverse from users to favorite books, but to also traverse from book to the list of users who have favorite'd, and if so, you may want such a list in the book object. No matter how useful, in my mind, this would constitute derived data rather than essential data, so I would probably not support update-actions/commands from that direction (e.g. from the book object).

(In persistence we are likely to model a (first-class) relation table that supports traversal in both direction.)

As far as bookmarks and ratings, I think these are fundamental domain concepts and merit their own entities and associations (and actions).

In collections, like the library, usually we let an entity representing the collection manage the admitting and removing of members rather than having the member add itself to the collection. This is because the responsibility for preserving and modifying the state associated with the relationship (collection membership) more naturally rests with the collection rather than the member.

Erik Eidt
  • 33,747
  • Generally its said that we must think about the behaviour of the objects first or message passing so I thought it masked sense to pass a favorite message to a book rather than user. – CodeYogi Sep 28 '17 at 06:53
  • Ok, and what i'm saying is that sometimes, to identify where a responsibility belongs (a command or update action), we can consider the domain information (state) and the ownership of that knowledge. Managing business/domain information/knowledge/relationships is a responsibility. So, the owner of the business information is a good candidate for collecting and modifying that information, and thus providing the methods for such. From this perspective, users are much more the owners of favorites information than books are of being fav'ed. So, it makes more sense to me that a user fav's book. – Erik Eidt Sep 28 '17 at 15:55
  • In the this domain, the book is just a passive entity with virtually no responsibilities, whereas the user is an active, information owning entity. – Erik Eidt Sep 28 '17 at 15:59
  • Also, I agree that there is no right way but there are some wrong ways which should be avoided. – CodeYogi Sep 29 '17 at 18:07
  • So, basically from your answer I can infer that first I need to understand relationship between the objects first so, here the user, book relationship is many to many hence user can be responsible for managing that relation. Now, what if I want to add the feature of renting the book? the book cannot be rented to many users at the same time but different copies to different users so, does this make it one to many relationship between user and books? – CodeYogi Oct 19 '17 at 13:53
  • For rent/sale, we need to distinguish between references to a published book e.g. by ISBN or title, and individual copies of books, which are actually rentable/salable. When we speak of ratings, favorites (and/or bookmarks), I think of rating the (authored content of a) published book title rather than an individual rentable/salable copy, which would be have a quite different meaning (e.g. this copy of a book is very used condition). – Erik Eidt Oct 19 '17 at 15:17
  • I would expect a library of rentable/salable copies to function as an inventory, e.g. noting copies and having tallies of titles (for a given ISBN). There is a rental relationship between users and a copy of a book from a library. – Erik Eidt Oct 19 '17 at 15:17
2

I start decomposing my domain with a design story. What is my domain about? What's important? What are the primary concepts? What actions should be made (without identifying by whom at the moment)? What are the borders of my domain, what am I modeling and what am I not modeling?

After that I identify the main concepts that form my domain. Probably some prominent relations could be illustrated on a semantic net. So we come up with candidate objects. They should have some clear responsibilities to be useful.

Then I come with use cases. My candidate objects apparently take part in them. That's how I can have an idea what responsibility should every object have.

Things I keep in mind while modeling my problems space:

  • Objects are active and smart. They possess all resources they need to implement their responsibilities.
  • Responsibilities should be stated in an active voice. It's a consequence of a previous statement.

So talking about your domain.

Design story

I'm modeling a library. Any person can visit it. After registration, he or she should be able to search a book from a library, bookmark it, favorite it or rate it.

So from this design story I make a conclusion that I'm not modeling a domain of new books receipts. It's apparently a separate sub-domain with its own responsibilities. It represents a different business-capability, in other words.

Candidate objects

A library, a user, a book.

Use cases

A user searches for a book.

It might seem that it's a user's responsibility. But object thinking implies objects to be active and encapsulated. A user can not dig in library's internals. The library is responsible for information it possesses. It correlates with GRASP's information expert concept. So if a user wants to search a book, he should ask a library.

A user bookmarks a book.

A book might not know anything that it's bookmarked by somebody. It's not its business. Only a user cares that he or she bookmarked some book. So it's definitely a user's responsibility.

... and so on.

Code

interface User
{
    public function bookmark(Book $book);

    public function addToFavourites(Book $book);

    public function rate(Book $book);
}

interface Library
{
    public function search(Book $book);
}

Further thoughts

Chances are that every user's action -- adding to favourites, or bookmarking, or rating, ends up with its own concept with their own responsibilities. So the implementation could be the following:

class Bookmark
{
    private $book;
    private $user;

    public function __construct(Book $book, User $user)
    {
        $this->book = $book;
        $this->user = $user;
    }

    public function highlight()
    {
        // ...
    }
}

class Rate
{
    private $book;
    private $user;

    public function __construct(Book $book, User $user)
    {
        $this->book = $book;
        $this->user = $user;
    }

    public function discard()
    {
        // ...
    }
}

This concepts could be operated upon in application's entry point, so probably the User could be discarded of this responsibilities.

And here is another example of the ways to decompose a domain.

1

From a high level view you seem to have modeled correctly this business, however there is a rule that you haven't respected: you should reference other Aggregates only by ID. This rules will help you separate the Aggregates better because you can't access the other Aggregate properties from an Aggregate.

So, your code should look something like this:

interface Book { 
    void bookmark(UserId userId); 
    void favorite(UserId userId); 
    void rate(UserId userId); 
} 
interface Library { 
    //List<Book> search(); - // this should be a domain service
    void addBook(BookId bookId); // CQS - command
    void removeBook(); // CQS - command
    List<BookId> allBooks(); 
} 

Searching the library should be a domain service's responsibility because it involves multiple Aggregates and it is used only for reading.

You should also follow the CQS pattern.