Java – Simple and Composite transactional services: Question about separation of concerns and transactions

design-patternsjavan-tierspring-mvctransaction

I believe I know the answer to this but I'm looking for any holes or anything I may be missing.

This is focused on Spring and Java but could really apply to any programming stack.

Anyway, we have a typical service layer annotated with @Transactional in Spring. For example, we could have:

EmailService
OrderService
HistoryService

Now, the user performs an action that calls a RESTful service that does the following:

orderService.create(....);    // wrapped in @Transactional
historyService.create(....);  // wrapped in @Transactional
emailService.emailUser(....); // wrapped in @Transactional (also annotated with @Async)

While all of these are called from a controller (which is NOT @Transactional), if either the order service or the history service fail, I want both to be rolled back and the email service would abort.

I don't like mixing the services. I think it would be ugly to have the order service call the history service just so that both of them are in the same transaction boundaries.

My first instinct was to create a hybrid service that would wrap both services together. Something like:

@Transactional
public void orderEntryService.create(....) {
    orderService.create(....);    // STILL wrapped in @Transactional
    historyService.create(....);  // STILL wrapped in @Transactional
}

This way, my controller could be:

public String create(...) {
    orderEntryService.create(...);

    emailService.emailUser(...);    // this is @Async 
                                    // and will never be called if previous 
                                    // orderEntryService.create fails
}

While I think this keeps my service layer cleaner, it can quickly start adding up to bulky and forgetful "aggregate" service classes. How to handle this?

Best Answer

I understand your doubts, because such service-mixins don't look natural for me either. But why? Strictly speaking, even in complete SOA architecture service composition is not forbidden. But your case is different. Your services are not independent units located in own processes with independent transaction management. Your services are just level in your single-process architecture, plus methods of your services are wrapped in database-transactions. So, service composition could lead to nested transactions which itself is not a problem for Spring, but still looks unnatural for me. Plus, such composition could lead to circular dependencies between your services which is evil without a doubt.

You could solve your problem by adding one more level(let's say DataAccess) in your architecture, which will contain repositories: EmailRepository, OrderRepository, HistoryRepository. Here you will have methods for managing emails, orders, history etc: adding, updating, deleting, querying. And you could share these repositories in your different services, having transaction wrappers where they are now - around your services.

It's just the simplest solution that could help you, not talking about more sophisticated approaches such as DDD.