3

Scenario

Let's say there is a Shop entity in my app. And two types of users: a Customer and an Admin. When a customer buys anything at a shop, that customer's visit is registered in the system (there's a visit counter per customer-shop).

So let's assume a shop is modeled the following way:

class Shop {
   id: number;
   name: string;
   postalAddress: PostalAddress;
   categories: Category[];
}

but, from the customer's point of view, the property numberOfVisits: number at the Shop entity would make a lot of sense too:

class Shop {
   id: number;
   name: string;
   postalAddress: PostalAddress;
   categories: Category[];
   numberOfVisits: number; // this makes sense for customers
}

This would depend on the use case that is being executed:

Use Case 'A': As an admin user I would like to list all the shops that exist in the system.

In this case dealing with Shop entities without any visit counter would be fine.

Use Case 'B': As a customer user I would like to list all the shops I have visited (including the number of visits).

In this case the customer would expect to have some shops listed and it is interested in the number of visits. So, dealing with Shop entities that have the numberOfVisits property would be important.

Design approaches

I came across several design approaches but none of them has yet convinced me.

Approach #1

Having a unique Shop entity with the visit counter property for every case:

class Shop {
   id: number;
   name: string;
   postalAddress: PostalAddress;
   categories: Category[];
   numberOfVisits: number;
}

What I don't like about this approach is having one entity with fields that wouldn't make sense in certain use cases or application contexts (even though they would be set to null). I don't feel it is a very clean solution.

Approach #2

Having several Shop entities:

class Shop {
   id: number;
   name: string;
   postalAddress: PostalAddress;
   categories: Category[];
}
class ShopWithVisits {
   id: number;
   name: string;
   postalAddress: PostalAddress;
   categories: Category[];
   numberOfVisits: number;
}

or even:

class Shop {
   id: number;
   name: string;
   postalAddress: PostalAddress;
   categories: Category[];
   numberOfVisits: number;
}
class ShopWithVisits extends Shop {
   numberOfVisits: number;
}

I find this somehow better than #1 because there it speaks about the different representations of the same entity in different use cases or contexts. But still, not sure if this could somehow be improved.

Questions

What is your opinion on this? Which approach do you feel is better and why? Is there any other approach I could follow to improve my design?

  • For the customer and admin, are you referring to what the user is seeing on the UI? In that case, have a single model with all of the fields you care about, and then have a customer view and admin view that returns a representation with the data you care about. This is a common approach in APIs where you have a list and detail view. The list view has most of the information and the detail view has all of it, but both views are pulling from the same data. – Jesse Sep 6 '19 at 15:20
  • @Jesse I was wondering, wouldn't it be somehow messy having a single class where you'd add a field anytime you need it (this class could grow up big in time and mix different context cases in the same place)? Wouldn't it be cleaner if your model could express the different domain contexts by splitting it across different classes? – charliebrownie Sep 9 '19 at 10:02
2

I am giving you the answer to your question based on a typical "layered" design.

Let me try to put your description into the context of an application design. You should differentiate between "data" and "service" parts. Executing "queries" like you described in the use cases means, getting information from some storage, returing some part of your data model.

The data model itself usually does not know about the services and making a design you should consider dependencies here:

For example:

  • Can a shop exist without "visits"? Probably yes.
  • Can a customer exist without "visits"? Probably yes.

That means, that the data classes for "Customer" and "Shop" should neither know about the concepts of visits. This can lead to a class design like in the below picture.

In blue you can see the data model.

It is important to design it in that way - otherwise you will not be able to load a lists of shops or customers without loading possible millions of visits into your class instances!

The queries themselves can be handled via "services", accessing some storages, which themself will have methods for receiving the necessary data.

example Design

  • Interesting... would the queries be the use-cases, in this case? – charliebrownie Sep 9 '19 at 11:13
  • 1
    Exactly. The important thing is to differentiate between the "data model" and the operation of your system. The Use Cases describe an "interaction" with your system, which could be handled by a "Service", e.g. a "Query" or a "AdminService" or whatever you want to call it. These "interactions" operate on the data model in a read / transform / update fashion. The data model itself usually are simply value classes. Here you should be headed for minimal dependency. Therefore you need to ask the question: Could A exist without B? Does A know about B? etc. – Mathias Mamsch Sep 9 '19 at 11:21
3

In my opinion, having a shop for each customer is a needless duplication of shop data. Furthermore, I'm reluctant to use inheritance for that case.

If you record visits then why don't you just have a class that would hold all those visit entries like below (pardon the syntax, I have no clue what language you are using)

class CustomerVisitEntry 
{
    shop: Shop;
    customer: Customer;
    dateVisit: DateTime;
    itemsPurchased: ICollection<ShopItem>;
    //literally any useful information about a visit
}

class CustomerVisitLog
{
    entries: ICollection<CustomerVisitEntry>
}
  • 1
    Great, this pretty much clears it up! Customers to Shops is clearly a many-to-many relationship, so normalization would definitely improve the design by enforcing the creation of a new entity that captures the intermediate relationship [Customer --> VisitEntry <-- Shop]. I know, databases are better treated as "implementation details" but you can always borrow some efficient design decisions from the "realm" and translate them to O-O world! – Vector Zita Sep 13 '19 at 16:51
1

I would suggest a different approach. Both DDD, but especially OO is about modeling behavior. Instead of having a Shop with data, you should concentrate on all the methods the Shop is supposed to support. These method names should also come from the ubiquitous language, i.e. the business language.

If you see your object used in completely different contexts, where one set of methods are used, and in a different context a completely different set, that is a good indication that the object should be "split". In DDD terms, you probably uncovered "bounded contexts".

Note, we still didn't have any data whatsoever. Now, that your model expresses the domain and most of the domain behavior, you can add the data the object needs. Don't worry about duplications and de-normalizations too much, it has a completely different meaning in code than in the database.

  • I was thinking about it, about contexts... so, correct me if I am wrong: are you suggesting having two representations of Shop, and use one or another depending on the context? let's say, for context A: class Shop { prop1; prop2; propA; } and for context B: class Shop { prop1; prop2; propB; propC; }, etc. – charliebrownie Sep 9 '19 at 10:10
  • 1
    I was saying that I don't know whether you have two contexts, but the way to determine that is not with data, but with behavior. If you have different contexts then yes, two different objects are a good way to go. – Robert Bräutigam Sep 9 '19 at 10:27
  • Get it! By the way, "Instead of having a Shop with data, you should concentrate on all the methods the Shop is supposed to support.", great advice! – charliebrownie Sep 9 '19 at 16:33
0

Based on the use-cases, I would set up the data like this:

class Shop {
    static allShops: [Shop]
    id: number
    name: string
    postalAddress: PostalAddress
    categories: [Category]
}

class User {
    id: number
    visited: [Shop] // as an unordered set
}

Then the Admin class can call Shop.allShops to get a list of all the shops in the system, and the User class can call self.visited to get a list of all the shops that it visited.

There is nothing in either use-case that implies that the number of visits are important so no reason to model that.

If visit count is important for some other use-case then I would likely extend the model like this:

class User {
    id: Int;
    timesVisited: [Shop: Int]
}
  • Sorry I was not clear at all, just updated the use case description: number of visits at a certain Shop would be important from the point of view of a customer. From the admin's context, number of visits make no sense... on the other hand, from the customer's contex they do: let's say I want the list of shops available, I would also want to be able to know how many times I visited each. – charliebrownie Sep 9 '19 at 10:21
  • I guess it's a good thing I covered that possibility then. – Daniel T. Sep 9 '19 at 10:40

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.