2
votes

The question is mostly a design question (somewhat related to ddd). Sorry about the contrived example:

Assume, you have (domain) classes representing different types of fruits: apple, cherry and so on. Now suppose you have to implement some behavior of pressing out the juice. A caller should be able to invoke squeezing without knowing which specific fruit he's got.

Where should I put this behavior?

Surely, one could define a fruit interface / base class function

Fruit#squeeze()

and let all subclasses implement their own behavior. Now a caller could simply do something like this:

Fruit f = new Cherry();
f.squeeze();

But what should be done if squeezing isn't as simple and involves more complex behavior like calling different external services, for each a fruit a different one like

AppleJuicerService#squeeze(Apple a)

and

CherryJuicerService#squeeze(Cherry c)

? It feels wrong to call services from a domain class.

I've read about the double dispatch pattern which seems not to fit here, as every subclass needs a different service.

My question would be: What can be done here to get a "clean" design?

EDIT:

Thanks for all your answers so far. I'll try to clarify the problem a bit. I'll try to give another, hopefully less contrived example for the problem I'm trying to state here:

Consider a Message base class which allows to show its content as a String.

interface Message {
    String showContent();
}

Now suppose we have different types of messages like an EMailMessage:

class EMailMessage implements Message {

    //some specific parameters for email
    private EmailAddress recipientEmail;

    public String showContent() {
        //here the content would be converted to string
        return "the content of an EMail" 
    }
}

Another type would be an SMSMessage:

class SMSMessage implement SMSMessage {

    //some specific parameters for SMS
    private TelNumber recepientTelephoneNumber;

    public String showContent() {
        //here the content would be converted to string
        return "the content of a SMS"
    }
}

Furthermore suppose, Messages are modeled as Entities and therefore can be persisted in a database. Though quite technically, assume that some Dependency Injection Framework like Spring is used to inject dependencies.

In analogy to the fruit example, consider we have to implement a send() behaviour which sends the Message to the recipient. Furthermore, assume that sending an EMail involves different logic than an SMS. Now, the question: Where should one put the logic of sending a Message?

Usually I'd opt to create a service for sending an SMS for example which would encapsulate e.g. the API of an SMS service provider. Furthermore, I'd create another service to encapsulate sending an EMail.

interface SendMessageService<T extends Message> {
    void send(T message);
}

class SendEmailService extends SendMessageService<EMailMessage> {
    public void send(EMailMessage message) {
        //send the EMail
    }
}

class SendSMSService extends SendMessageService<SMSMessage> {
    public void send(SMSMessage message) {
        //send the SMS
    }
}

The drawback of this approach is that you cannot send a Message without determining its concrete subclass, i.e. something like the following is not directly possible

List<Message> messages = //Messages of different types 

SendMessageService service = //???

for (Message m : messages) {
    service.send(m);
}

Surely one could create a factory for creating Services according to the specific type of message. But that somewhat means cloning the inheritance hierarchy of Message. Is there some better way to achieve the desired result? Or am I missing something? Or would it be better to somehow inject the service into the entity?

3
Maybe the example is just too contrived here - Fruit#squeeze() has a void return type, what does it do to Fruit? - Gyro Gearless
+1 we can't provide a good answer if you don't explain what squeeze could possibly stand for and why you need a service in the first place. (BTW, it is perfectly legal to call a Domain Service from an Entity even though you'll see it the other way around more often) - guillaume31
@guillaume31 I've tried to edit the question to further explain the issue - John

3 Answers

1
votes

You can delegate the work to a SqueezeBehavior interface and let each implementation define how to squeeze a Fruit or specific Fruit. This is a raw idea (it means it can be improved but is good as a first step):

interface SqueezeBehavior<T> {
    void squeeze(T squeezeMe);
}

interface FruitSqueezeBehavior<T extends Fruit> extends SqueezeBehavior<T> {
}

class FruitSqueezer implements FruitSqueezeBehavior<Fruit> {
    public void squeeze(Fruit fruit) {
        System.out.println("squizing any fruit");
    }
}

class AppleSqueezer implements FruitSqueezeBehavior<Apple> {
    public void squeeze(Apple apple) {
        System.out.println("squizing apple");
    }
}

class CherrySqueezer implements FruitSqueezeBehavior<Cherry> {
    public void squeeze(Cherry cherry) {
        System.out.println("squizing cherry");
    }
}

class FruitService {

    public void foo(Fruit fruit) {
        FruitSqueezeBehavior fruitSqueezer = ...
        fruitSqueezer.squeeze(fruit);
    }
}
0
votes

Have a baseclass Fruit which defines the standard behaviour. When you have to use a more complex implementation you can override the appropriate method.

class Fruit {
 public void Squeeze(){
  // Standard squeeze behaviour
 }
}

class Apple extends Fruit {
 @Override
 public void Squeeze(){
  // Complex squeeze behaviour
 }
}

class Cherry extends Fruit {
 // Nothing special, cherries are easy to squeeze
}

If you have to define specific implementations for specific types, you will always have to define the behaviour somewhere. If this is too much for one method then you can call a more detailed class to do it for you.

You could work with a factory and do something like this

class FruitManipulator {
 void Squeeze(Fruit f){
  // Switch over fruit, create new service depending on the type
 }
}

interface JuiceService<T extends Fruit> {
 void Squeeze(T f);
}

class AppleJuiceService implements JuiceService<Apple> {
 void Squeeze(Apple apple){
  // Do your thing
 }
}

And use it like this:

FruitManipulator service = new FruitManipulator();
service.Squeeze(new Apple());

You might want to find a better example though: the Squeeze() analogy isn't easy to work with. Perhaps expand on what a squeeze actually means?

0
votes

You may consider DomainEvents. This helps you decouple Domain models from external service(usually stateless bean need injected)

interface Fruit {
    void squeeze();
}

class Apple implements Fruit {

    @Override
    public void squeeze(){
        // domain rules validations
        DomainEvents.raise(new AppleSequeezedEvent(this));
    }
}

class Cherry extends Fruit {
    @Override
    public void squeeze(){
        // domain rules validations
        DomainEvents.raise(new CherrySequeezedEvent(this));
    }
}

class Banana extends Fruit {
    @Override
    public void squeeze(){
        // domain rules validations
        // hmm...No one cares banana...
    }
}

class DomainEvents {
    private static List<DomainEventHandler> handlers = new ArrayList<DomainEventHandler>();

    public static void register(DomainEventHandler handler) {
        this.handler.add(handler);
    }

    public static void raise(DomainEvent event) {
        for (DomainEventHander handler: handlers) {
            if (handler.subscribe(event.getClass()) {
                 handler.handle(event);
            }
        }
    }
}

Now when you test apple, you could register some handler mock/stub:

@Test
public void tellsAppleIsSqueezed() throws Throwable {
    DomainEventHandler stub = new FruitSqueezedEventHandlerStub(Apple.class);
    DomainEvents.register(stub );

    Apple apple = new Apple();

    apple.squeeze();

    //assert state change of apple if any before you publishing the event
    assertThat(stub.getSqueezed(), sameInstance(apple));
}

You can test the real handler in their own unit test cases.

But I think this solution add extra complexity.