Where should business logic (and what is that?) really live and how to do that with Spring?
Asked Answered
W

1

8

I was just reading this article:

http://www.tutorialized.com/view/tutorial/Spring-MVC-Application-Architecture/11986

which I find great. It explains the layer architecture nicely and I was glad that the architecture I'm working with pretty much is what he describes.

But there's one thing, that I don't seem to get:

First: what exactly is business logic and what is it not? In the article he says (and he's not the only one), that business logic should go in the domain model. So an Account class should have an activate() method that knows how to activate an Account. In my understanding this would involve some persistence work probably. But the domain model should not have a dependency of DAOs. Only the service layer should know about DAOs.

So, is business logic just what a domain entity can do with itself? Like the activate()method would set the active property to true, plus set the dateActivated property to new Date() and then it's the service's task to first call account.activate()and second dao.saveAccount(account)? And what needs external dependencies goes to a service? That's what I did until now mostly.

public AccountServiceImpl implements AccountService
{
  private AccountDAO dao;
  private MailSender mailSender;

  public void activateAccount(Account account)
  {
     account.setActive(true);
     account.setDateActivated(new Date());
     dao.saveAccount(account);
     sendActivationEmail(account);
  }
  private void sendActivationEmail(Account account)
  {
    ...
  }
}

This is in contrast to what he says, I think, no?

What I also don't get is the example on how to have Spring wire domain objects like Account. Which would be needed should Account send its e-mail on its own.

Given this code:

import org.springframework.mail.MailSender;  
import org.springframework.mail.SimpleMailMessage;  

public class Account {  
   private String email;  
   private MailSender mailSender;  
   private boolean active = false;  

   public String getEmail() {  
      return email;  
   }  

   public void setEmail(String email) {  
      this.email = email;  
   }  

   public void setMailSender(MailSender mailSender) {  
      this.mailSender = mailSender;  
   }  

   public void activate() {  
      if (active) {  
      throw new IllegalStateException("Already active");  
      }  

      active = true;  
      sendActivationEmail();  
   }  

   private void sendActivationEmail() {  
      SimpleMailMessage msg = new SimpleMailMessage();  
      msg.setTo(email);  
      msg.setSubject("Congrats!");  
      msg.setText("You're the best.");  
      mailSender.send(msg);  
   }  
}

If I use Hibernate, I could use the DependencyInjectionInterceptorFactoryBean in order to wire mailSender. If I use JDBC instead, I'd really write the follwing cumbersome code? Plus, also when I create a new instance for Account in a MVC controller, for let's say populating it to a model??

  BeanFactory beanFactory = new XmlBeanFactory(  
     new ClassPathResource("chapter3.xml"));  
  Account account = new Account();  
  account.setEmail("[email protected]");  
  ((AutowireCapableBeanFactory)beanFactory).applyBeanPropertyValues(  
        account, "accountPrototype");  
  account.activate(); 

This is not reliable and very cumbersome, no? I'd have to ask myself where that object has been created, whenever I see an instance of Account. Plus, if I would go with this approach: I have not a single appContext.xml I could pass, but several, one for persistence, one for the service config. How would I do that? Plus, that would create a completely new context every time such an instance is created or am I missing something?

Is there no better solution to that?

Any help is greatly appreciated.

Webbing answered 27/7, 2012 at 15:2 Comment(0)
P
8

I think send activation email action is not a part of a business-layer here, your domain logic here is the account activation action, that piece of logic should live in the DomainObject with name Account ( activate() method ). The send activation email action is the part of infrastructure or application layers.

Service is the object that handles account activation request and connects business-layer and others. Service takes the given account, activates them and performs send activation email action of MailSenderService or something like this.

Short sample:

public AccountServiceImpl implements AccountService
{
 private AccountDAO dao;
 private MailSenderService mailSender;

 public void activateAccount(AccountID accountID)
 {
   Account account = dao.findAccount(accountID);
   ....
   account.activate();
   dao.updateAccount(account);
   ....

   mailSender.sendActivationEmail(account);
 }

}

The next step that I can suggest is a complete separation of business layer and a layer of infrastructure. This can be obtained by introducing the business event. Service no longer has to perform an action to send an email, it creates event notifying other layers about account activation.

In the Spring we have two tools to work with events, ApplicationEventPublisher and ApplicationListener.

Short example, service that publish domain events:

public AccountActivationEvent extends ApplicationEvent {
    private Account account;

    AccountActivationEvent(Account account) {
       this.account = account;
    }

    public Account getActivatedAccount() {
       return account;
    }
}

public AccountServiceImpl implements AccountService, ApplicationEventPublisherAware
{
 private AccountDAO dao;
 private ApplicationEventPublisher epublisher;

 public void setApplicationEventPublisher(ApplicationEventPublisher epublisher) {
    this.epublisher = epublisher;
 }

 public void activateAccount(AccountID accountID)
 {
  Account account = dao.findAccount(accountID);
  ....
  account.activate();
  dao.updateAccount(account);
  ....

  epublisher.publishEvent(new AccountActivationEvent(account));
 }

}

And domain event listener, on the infrastructure layer:

public class SendAccountActivationEmailEventListener 
      implements ApplicationListener<AccountActivationEvent> {

  private MailSenderService mailSender;

  .... 

  public final void onApplicationEvent(final AccountActivationEvent event) {
   Account account = event.getActivatedAccount():
    .... perform mail ...
   mailSender.sendEmail(email);
  }
 }

Now you can add another activation types, logging, other infrastructure stuff support without change and pollute your domain(business)-layer.

Ah, you can learn more about spring events in the documentation.

Plinth answered 30/7, 2012 at 10:39 Comment(2)
masted, thanks for your answer. So, basically it's true what I suggested, that business logic is "stuff that the business entity can do on its own, without using other layers". The rest is a service task (or infrastructure or whatever you call it). Makes some sense to me, however intuitively, I would call the decision whether to send a confirmation e-mail or not a "business decision", I think that's what makes me confused.Webbing
The decision whether to send a confirmation e-mail or not - yes is a "business decision", but sending is not a business task. :) And I'm not sure that the decision is the area of responsibilities of Account. This is most likely a task for AccountService.Plinth

© 2022 - 2024 — McMap. All rights reserved.