views:

255

answers:

3

This question is, at it's core, a design question. I'll use a Java/JEE example to illustrate the question.

Consider a web-mail application which is built using JPA for persistence and EJB for the services layer. Let's say we have a service method in our EJB like this:

public void incomingMail(String destination, Message message) {
    Mailbox mb = findMailBox(destination); // who cares how this works
    mb.addMessage(message);
}

This is seemingly a reasonable business method. Presumably, the Mailbox object will still be attached and it will seamlessly save the changes back to the database. After all, that is the promise of transparent persistence.

The Mailbox object would have this method:

public void addMessage(Message message) {
    messages.add(message);
}

Here's where it gets complicated -- assume we want to have other mailbox types. Say we have an AutoRespondingMailbox which automatically responds to the sender, and a HelpDeskMailbox which automatically opens a helpdesk ticket with each email received.

The natural thing to do would be to extend Mailbox, where AutoRespondingMailbox has this method:

public void addMessage(Message message) {
    String response = getAutoResponse();
    // do something magic here to send the response automatically
}

The problem is that our Maibox object and it's subclasses are "domain objects" (and in this example, also JPA entities). The Hibernate guys (and many others) preach a non-dependent domain model -- that is, a domain model that does not depend on container/runtime provided services. The issue with such a model is that the AutoRespndingMailbox.addMessage() method cannot send an email because it can't access, for example, JavaMail.

The exact same issue would occur with HelpDeskMailbox, as it could not access WebServices or JNDI injection to communicate with the HelpDesk system.

So you're forced to put this functionality in the service layer, like this:

public void incomingMail(String destination, Message message) {
    Mailbox mb = findMailBox(destination); // who cares how this works
    if (mb instanceof AutoRespondingMailbox) {
        String response = ((AutoRespondingMailbox)mb).getAutoResponse();
        // now we can access the container services to send the mail
    } else if (mb instanceof HelpDeskMailbox) {
        // ...
    } else {
        mb.addMessage(message);
    }
}

Having to use instanceof in that way is the first sign of a problem. Having to modify this service class each time you want to subclass Mailbox is another sign of a problem.

Does anyone have best practices for how these situations are handled? Some would say that the Mailbox object should have access to the container services, and this can be done with some fudging, but it's definitely fighting the intended usage of JPA to do that, as the container provides dependency injection everywhere except in Entities, clearly indicating that this isn't an expected use case.

So, what are we expected to do instead? Liter-up our service methods and give-up polymorphism? Our objects automatically become relegated to C-style structs and we lose most of the benefit of OO.

The Hibernate team would say that we should split our business logic between the domain layer and the service layer, putting all of the logic that's not dependent on the container into the domain entities, and putting all the logic that is dependent on the container into the services layer. I can accept that, if someone can give me an example of how to do that without having to completely give-up polymorphism and resorting to instanceof and other such nastiness

A: 

One option (and maybe not the best option) would be to wrap the object inside of an "executor" object. The executor object would contain the service layer information, and the internalized data object would contain the domain information. You could then use a factory to create these objects, thereby limiting the scope of the "instanceof" methods or similar elements, and then the different objects would have some sort of common interface to use so they could execute on their data objects. It is kind of a mix between the command pattern - you have the command object as the executor - and a state pattern - the state is the current state of the data object - although neither is an exact fit.

aperkins
But we do all this in the name of keeping container dependencies out of our domain model. To what end? Just to make things harder?
TTar
To allow for different front end services to use the same back end system, is my understanding
aperkins
+4  A: 

a mailbox is a mailbox...

...but an autoresponding mailbox is a mailbox with some rules attached to it; this is arguably not a subclass of mailbox, but is instead a MailAgent that controls one or more mailboxes and a set of rules.

Caveat: I have limited experience with DDD, but this example strikes me as based on a false assumption, e.g. that the behavior of applying rules belongs to the mailbox. I think applying rules to messages is independent of the mailbox, i.e. the recipient mailbox may be only one of the criteria used by filtering/routing rules. So an ApplyRules(message) or ApplyRules(mailbox, message) service would make more sense to me in this case.

Steven A. Lowe
I think you missed the intent of my question. Forget the specifics of the example, and assume that some subclasses of an Entity needed behavior which relied on some container-provided service.
TTar
I agree with this. The issue is that your domain object is no longer a simple holder of data, but now you have attached behavior (with impacts on persistence) to it. Personally this feels like something that should be handled at the service level.
matt b
So if a domain object is a simple holder of data, isn't my domain object just a struct? Isn't the definition of object oriented design that data and behavior are combined into objects?
TTar
@[TTar]: sorry, I can't make the assumption you request, because it does not agree with the semantics of the example given. In other words, I can't get past the design issue (arguably, design *mistake*) that is assumed by your question. It would make more sense to me in your example to have an ApplyRules(mailbox, message) service to keep everything separated.
Steven A. Lowe
@[TTar]: see edit (caveat). Good luck!
Steven A. Lowe
@Steven -- Right. Subclassing Mailbox in this case to add rules seems to be to be a violation of the SRP.
Dave Markle
@Steven - Ok. Can we assume that a mailbox which has auto response capabilities may need additional data stored in the mailbox object, like the auto-response content itself? In that case, you still may need to subclass mailbox to store this additional data. Therefore, you end up with both a subclass of Mailbox and with an ApplyRules() method which must be potentially modified to account for this behavior. Right?
TTar
@[TTar]: uh...no. Because the auto-response behavior does not logically belong to the mailbox [a mailbox is a named container, nothing more]. But feel free to implment it this way if you like, it will probably still work, and may never give you any problems in the future. I wouldn't do it that way, though.
Steven A. Lowe
+5  A: 

You're missing something: it's completely sensible for the Mailbox object to depend on an interface that's provided at runtime. The "don't depend on runtime services" is correct, in that you shouldn't have compile-time dependencies.

With the only dependency being an interface, you can use an IoC container like StructureMap, Unity, etc. to feed your object a test instance as opposed to a runtime instance.

In the end, your code for an AutoRespondingMailbox might look like this:

public class AutoRespondingMailbox {
    private IEmailSender _sender;

    public AutoRespondingMailbox(IEmailSender sender){
        _sender = sender;
    }

    public void addMessage(Message message){
        String response = getAutoResponse();
        _sender.Send(response);
}

Note that this class does depend on something, but it's not necessarily provided by the runtime - for a unit test, you could easily provide a dummy IEmailSender that writes to the console, etc. Also, if your platform changes, or requirements change, you can easily provide a different IEmailSender on construction that uses a different methodology than the original. That is the reason for the "limit dependencies" attitude.

Harper Shelby
I think that the Hibernate guys would argue otherwise. They really believe that the business logic can/should be split up. I don't see how it can be split-up without making the whole thing into a mess.
TTar
How is this not splitting the business logic? The only logic in the AutoRespondingMailbox is that it should get a response from somewhere, and send that automatically. It isn't dependent on anything other than the existence of a particular interface. That interface can be provided in any number of ways, many of which are not related to the compiled code at all.
Harper Shelby