1

I've asked a previous question last month, about whether an Entity should access a Repository, and although it looks like most people think they shouldn't, I have to admit it's hard for me to get convinced.

I have a use case for which I really can't think of any (reasonable) way to do the logic without injecting the Repository in my Entity:

We have a Store, which is assigned a Zone (city, district, ... - user defined). To reduce the workload of the employee in charge of adding Stores to the database, and to ensure consistency, we don't ask him to select the Zone in which he wants to add the Store. He just zooms on a map, clicks to pinpoint the Store location, and saves. The application then has to find the most relevant Zone for this location.

What I currently have is something like:

class Store
{
    protected Zone zone;
    protected Point location;
    protected ZoneRepository zoneRepository;

    public void setLocation(Point location)
    {
        Zone matchingZone = this.zoneRepository.findByLocation(location);
        if (matchingZone == null) {
            throw new ApplicationException(
                "The store location must be within a zone boundary");
        }
        this.location = location;
        this.zone = matchingZone;
    }
}

Do you have any solid alternative which would support the commonly accepted opinion that this design is inherently bad?

Community
  • 1
  • 1
BenMorel
  • 34,448
  • 50
  • 182
  • 322

4 Answers4

3

It would not necessarily say that this design is bad. It just raises questions. Most of the time there is no need to use repository from within an Entity. Having Store look up its own zone based on geographical point seems a bit strange. It does not look like this responsibility belongs to Store.

Is point always required? What if the user would want to select zone from the list of 'Last 5 Zones'? Or from Country/City drop downs? Or from search text box? There is no need for point in these cases. Requiring point and repository would seems like tailoring domain model to UI model.

If you indeed need a point for every single Store then you may not need Zone field at the same time. What if you lookup Store's Zone dynamically, only when you need it? Using most up to date Point-to-Zone database.

Also if your Store class has 5 or 6 methods and only one of them needs zoneRepository field than the class is not very cohesive.

Dmitry
  • 17,078
  • 2
  • 44
  • 70
  • Very interesting link indeed. To answer your questions, yes the Location is always required, and we don't want to assign the Zone manually as the zone **must** match the Location (there are no two possible Zones for a Location), so we want to enforce setting the Zone by Location only (this was probably unclear in my question). Also, because we need to search for Stores within a hierarchy of Zones, it's quite inefficient to lookup the Zone dynamically. That said, your comment is very constructive and I understand and somewhat agree with your concerns about the single responsibility principle! – BenMorel Sep 03 '11 at 10:49
1

In terms of DDD for searching Location by point you should have separate service for it like LocationService which will encapsulate your repository inside. And this service should know nothing about User store entity and especially never include repository into the Entity.

Samich
  • 29,157
  • 6
  • 68
  • 77
  • So the Service is the link between my Entity and my Repository then? Can I inject the Service in the Store entity? – BenMorel Sep 03 '11 at 00:29
1

From the description you've provided it seems that Point is actually not part of your Ubiquitous Language - the Zone is. You don't set a point for store - you assign a Zone to it. In other words, the signature of this operation shouldn't be setLocation(Point location) - it should be assignZone(Zone locationZone). The translation between point selected by user and the zone should occur before domain model operation is performed. That's how I'd model it.

Example added after comments (C# if you don't mind - and shows only a concept). Code assumes command-based approach to performing user actions - that's how I tend to do it, but might be an Application Service or Controller even, depending on the structure of your application.

    public class StoreLocationHandler : Handles<LocateStoreCommand>
    {
        public void Handle(LocationStoreCommand command)
        {
            // Location contains coordinates as well as zone info and can be obtained only via LocationService
            Location location = LocationService.IdentifyZone(command.Coordinates); 

            Store store = StoreRepository.Get(command.StoreId);

            store.AssignLocation(location);

            // persist changes - either by Unit of Work or Repository
        }
    }

The point is that you don't have to craete everything inside your domain entities - Zone seems to be Value Object from perspective of Store. Besides, further separation of those two concepts might lead to additional possibilities, like identifying the zone not online but through some kind of background process (for the sake of performance or scalability). In addition, in my opinion it fits better considering Dependency Injection principle.

After all, domain doesn't care about how the zone is created or retrieved, it cares about association between Store, its location and the zone it falls into. Why should it be responsible for translation between a point and a zone? At least that's the way I see it.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
kstaruch
  • 1,289
  • 7
  • 7
  • Interesting approach indeed, but where should this translation happen then? Also, I forgot to mention that Location is actually part of the Store entity as well, I edited my code to reflect this. – BenMorel Sep 03 '11 at 00:34
  • I've added some explanation and an example. – kstaruch Sep 05 '11 at 14:31
  • I quite like your approach, but am a bit concerned about this contract with the Store, which basically allows any layer of the application to assign a mismatching Location and Zone to the itself. How do you enforce the fact that **only** the StoreLocationHandler, which is the only one to know the correct business rule matching the Location to the Zone, can call `store.assignLocation()`? What I liked in the original approach was that this rule was enforced in the Store itself, and no transgression was possible from anywhere in the application. – BenMorel Sep 05 '11 at 15:28
  • I see your point. If keeping consistency between point and zone matters, instead of passing these two as separate parameters I'd add another Value Object - let's call it Location which would contain information about coordinates and zone. I'll modify the sample code to reflect this. You may still argue, that now the logic is not enforced in Store, but in some Service or Repository and thus someone could create an artificial, invalid object and pass it to your domain. In this case disallow creating of objects outside of this Service/Repo. – kstaruch Sep 05 '11 at 17:01
1

I use the same approach and as you, couldnt get convinced that its a bad design. HOWEVER, I have interfaces to my repositories, so I dont use concrete implementations of ZoneRepository as this way it could become to hard, and depending on the context impossible to test and mock it.

The other point, as @Samich said, is that its sounds good to take the knowledge of finding a zone based on a point from the Store object as later you may wanna use this method at another place. If you imoplement a service for the Zones youll be then centralizing your code and avoiding redundancy as well.

Renato Gama
  • 16,431
  • 12
  • 58
  • 92
  • I totally agree with you on the fact that this kind of model is well compatible with unit testing by mocking repositories! About the Service, the knowledge of finding a zone based on a point is not in the Store object, it is actually in the ZoneRepository, which can also be accessed from anywhere else, isn't it? – BenMorel Sep 03 '11 at 00:39
  • Of course Benjamin, happens that I believe that Services are places in which you use to insert business logic and repositorires are responsible just for abstracting a way of fetch data fromanywhere, see, in the app I work we have cache-repos (mcached) each one of this repo have another repo inside, that I inject (spring.net) a SqlServer2008 implementation (*data oriented impl for repos*).. so if I have this kind of rule (if null then exeption) which is clearly located at your domain thus "a not centralized bussines logic" I would move it to a service and the service would then consume yourrepo – Renato Gama Sep 03 '11 at 01:06
  • I would even have interfaces for my repos in a way i could test/mock them too! (its actually the way I work).. after beign working for 4 month with this system architecture I found that it slows down a bit the pace you develop but you have the enourmous advantadge that your bussines logic are totally separated/modularized, you spend more time developing but saves on maintenance, and WHEN you need to maintain or find a bug it becomes much simpler. (just my point of view, of course hehe) – Renato Gama Sep 03 '11 at 01:10
  • That makes sense. I think I just didn't have the breakthrough yet to understand the concept of (and the need for) Services! How would you model my requirement then: would you inject the Service in the Store, or would you call the Service with a Location parameter, which would in turn assign both Location and Zone to the Store? – BenMorel Sep 03 '11 at 10:55