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?
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.
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.
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.
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.
精彩评论