views:

276

answers:

4

I am working with a legacy system that has an anemic domain model.

The domain has the following entity classses: Car, CarType, CarComponent, CarComponentType.

For each of these, there is a separate repository. There is also a number of services that access these repositories and contain basically all logic.

I need to implement a method that determines if a CarComponentType can be discontinued by the vendor. The logic is as follows: a component can be discontinued only if there are no existing cars with that component today.

Initially, I implemented this in a service class.

public boolean canBeDiscontinued(CarComponentType carComponentType) {
    List<Car> cars = carRepository.getCarsWithComponent(carComponentType);
    return cars.isEmpty();
}

This works - but this logic is used from several other places in the code. It might grow, and it looks like something that could fit inside the CarComponentType class instead:

public boolean canBeDiscontinued() {
    List<Car> cars = carRepository.getCarsWithComponent(this);
    return cars.isEmpty();   
}

However, I can't put it there, since it needs to access the repository (and as I understand it is a very serious antipattern for entities to be aware of the data access layer). When loading a component type I can't load all cars of that type since that could be thousands of objects. We are not using any ORM, so making a lazy loaded collection for not only be bulky but also very error-prone.

Is it more appropriate to actually have this method in a service class as I first did? Is it not important? Is there another alternative? Should I start refactoring from another starting point?

There is a similar question here. But my question relates to Java, so I don't think that solution is applicable in my case. Also, sorry in advance for using cars and components as my domain model. :)

+3  A: 

This sounds like a good candidate for the Specification pattern

Frederik Gheysels
In which layer would I put the implementation of the spec? Infrastructure?
Chris
Specification is an integral part of the Domain layer, since 'domain concepts' are implemented in the specification.
Frederik Gheysels
+4  A: 

Frederik Gheysels answer is good, although perhaps a bit short. To elaborate: In your case, you could start out by defininig an interface for your specification (excuse my C# syntax):

public interface ICarSpecification
{
    bool IsSatisfiedBy(CarComponentType carComponentType);
}

You can then create an implmenetation of ICarSpecification that uses your Repository. Something like this:

public class CanDiscontinueSpecification : ICarSpecification
{
    private readonly CarRepository carRepository;

    public CanDiscontinueSpecification(CarRepository carRepository)
    {
         this.carRepository = carRepository;
    }

    public bool IsSatisfiedBy(CarComponentType carComponentType)
    {
        return this.carRepository.GetCarsWithComponent(carComponentType).Any();
    }
}

You could stop right there, but the thing I don't particularly like about the Specification pattern is that it is not very discoverable. One possible solution would be to inject the Specification into the CarComponentType itself:

public class CarComponentType
{
    private readonly ICarSpecification discontinueSpec;

    public CarComponentType(ICarSpecification discontinueSpec)
    {
        this.discontinueSpec = discontinueSpec;
    }

    public bool CanBeDiscontinued()
    {
        return this.discontinueSpec.IsSatisfiedBy(this);
    }
}

Alternatively, if you don't like to carry around the Specification in every instance of the class, you could use Method Injection instead of Constructor Injection:

public bool CanBeDiscontinued(ICarSpecification spec)
{
    return spec.IsSatisfiedBy(this);
}

Such a method doesn't really add any value in terms of implementation, but is more discoverable.

Mark Seemann
Thanks for clarifying. If I had more specifications that could be combined this seems appropriate. In my case, I am unsure. Instead of injecting a repository into each entity, I inject a specification which *uses* a repository. Is that really so much better?
waxwing
Well, it decouples your class from the the repository and it's probably easier to unit test by passing in specification implementations that do what you want for a test.
Chris Kessel
Yeah: what chriskes just said :) Seriously, that makes the Specification just another Strategy, that may or may not depend on a Repository. Still, I included the Method Injection alternative just in case you really didn't like to have a universal dependency on the Specification.
Mark Seemann
I am reluctant. I see the benefit for decoupling, but it seems almost like the "Yet another useless layer antipattern" if I use this all over the place.
waxwing
@waxwing: Don't take my answer as an attempt to persuade you to do one thing or the other. I only meant it as an elaboration on possible directions you *could* take. Personally, I like to keep my Domain Entities as POCOs/POJOs, so injecting the Specification into the CarComponentType is not my preferred solution. As an alternative, you could simply have a boolean property on the class and have a Factory (that knows about the Specification) set it up correctly.
Mark Seemann
+1  A: 

I don't think it's an understatement to say I hate anemic domain models as much as the next man.

However given the system you are working on has already has established the anemic domain model/service (anti) pattern I think introducing additional patterns could be to the determent of the system when done in isolated cases as I believe you have here. Such a decision should be made by the team and should have clear benefits.

Also, IMHO, I think your original code snippets are simpler than the solutions proposed in other answers (no offense Mark and Frederik, just an observation :-) and that the more complicated solution doesn't really bring any benefits - I mean, the functionality is the same in both cases but the later uses more moving parts.

In terms of how to proceed, with a single example it's hard to say. Introducing an ORM (which you mentioned isn't currently used) could be one way to go as it should reduce the code in you service classes and would have clear benefits so I'd be tempted to start there, then once that's in place review the situation.

Nick Holt
Thanks. I had almost resigned to this answer myself, but I was wondering for academic purposes. I feel there *should* be a smarter solution that I'm just not seeing.
waxwing
+3  A: 

I don't think the "can this be discontinued" belongs in any of those classes. Who is responsible for determining if a part can be discontinued? Not the Car or the Car component. I think you were on the right track with your initial method implemented in a service. Perhaps you need an CarInventoryManagementService that's responsible for answering questions about car inventory items like:

carsUsingComponent( CarComponent comp )
canComponentBeDiscontinued( CarComponent comp )
etc

If you have multiple places in the code that need to ask inventory related questions, such as your "canBeDiscontinued" then it might make sense to create a service just with that responsibility.

Chris Kessel
This is a bit subjective. It's more often than not a borderline case whether the domain object should be "aware" of how it's used by clients. Maybe you are right in this case. But let's say it's a different function - something that unquestionably fits in CarComponentType but needs data not contained in the object itself. That is what I am more interesting in.
waxwing
I'm thinking responsibility driven design. Would a Car be the one responsible for determining if it's still in production or the creator of the Car? A Car might certainly know who produces it, but it really doesn't have any reason to hold the responsibility for making decisions about whether all Cars of it's type are, say, end of lifed. Domain objects generally should only answer questions about themselves. Questions that apply to all of a type of domain object are typically the responsibility of a Domain Object Service. I don't know this software obviously, but it's a general principle.
Chris Kessel