24

I've made a system that use JPA and Spring. For example, If I need to handle Accounts, I use a repository:

@Repository
public interface AccountRepository extends JpaRepository<Account, Long>

Then I create a service that use the repository:

class AccountServiceImpl implements AccountService {

  @Autowired
  private AccountRepository repository;

  @Transactional
  public Account save(Account account){
    return repository.save(account);
  }

...

Now, I've created a controller that handle the POST method for Accounts:

@Controller
public class AccountController {

@Autowired    
private final accountService service;

@RequestMapping(value = "/account", method = RequestMethod.POST)
        public ModelAndView account(@Valid Account account, BindingResult bindingResult) {
    ...
service.save(account);

The same for Customer, Products, Contacts, etc.

Now, let suppose that I also have a class called "registration" that containts enough data to create a customer, with his accounts, contact data and products(a lot of data). The action "confirm" to a registration is the one dedicated to do that:

@RequestMapping(value = "/confirm", method = RequestMethod.POST)
    public ModelAndView confirmRegistration(@Valid Registration registration, BindingResult bindingResult) {

Now my question: What is the correct way to call the save method for every repository?

1) Should I create the classes in the controller and then call the save method for every class created:

@RequestMapping(value = "/confirm", method = RequestMethod.POST)
        public ModelAndView confirmRegistration(@Valid Registration registration, BindingResult bindingResult) {
...
customerService.save(customer);
accountService.save(account);
contactDataService.save(contactData);
productService.save(contactData);
...

2) Call the saves of each service in the RegistrationService:

class RegistrationServiceImpl implements RegistrationService {

  @Autowired
  private AccountService accountService;
  @Autowired
  private CustomerService customerService;
  ....

  @Transactional
  public void confirm(Registration registration){
  ... here I create the object
  customerService.save(customer);
  accountService.save(account);
  }

3) Call the saves of each repository in the RegistrationService:

class RegistrationServiceImpl implements RegistrationService {

  @Autowired
  private AccountRepository accountRepository;
  @Autowired
  private CustomerRepository customerRepository;
  ....

  @Transactional
  public void confirm(Registration registration){
  ... here I create the object
  customerRepository.save(customer);
  accountRepository.save(account);
  }

I undertand if I have to use (1). But confused about option (2) and (3).

The question again:

Should/Can I use other services in a service? Or I have to use only repositories in a service?

What is the correct way to google the explanation of this? Sorry English is not my native language and I can't find the correct way to ask about this kind of design.

Mariano L
  • 1,809
  • 4
  • 29
  • 51
  • 1
    Consider transaction demarcation perhaps as a starting point. Services handle transactions for repositories. – K.Nicholas Aug 23 '18 at 00:35
  • 1
    googling advice: spring adopts terminology from domain-driven design, you could google domain driven design or DDD along with terms like service or repository for more conceptual explanations. – Nathan Hughes Aug 23 '18 at 17:50

2 Answers2

31

Services don’t always have to be transactional, but when you’re doing database work with JPA transactions are extremely important, because transactions make sure your changes get committed predictably without interference from other work going on concurrently. Spring makes it easy to make your services transactional, make sure you understand transactions so you can take full advantage of them.

You can use services within services, you can set up transaction propagation so they both use the same transaction or they can use separate transactions, there are valid cases for either of these. But I would suggest not doing what you’re doing here.

A service is a place you put business logic, especially business logic that you need to be transactional (all-or-nothing). It makes sense to organize your logic into services according to function, so that the service methods are actions taken by users playing some particular part.

But having a service for each type of entity isn’t really useful and I’d recommend against it. A service can have any number of repositories, you don’t have to wrap each one in its own service. (Tutorials show entity-specific services, or they may skip the service layer altogether, that’s because they want to show you framework features, and minimize the business logic. But real applications tend to have a lot of business logic.)

One problem with what you’re doing with entity-specific services: calling them in the controller one after the other means each one uses its own transaction. Creating transactions is slow and having separate ones opens you up to possible data inconsistencies. Having your business logic within one transaction limits your exposure to consistency issues to just those associated with your transaction isolation level.

Also I disagree with the idea that services should be converting between dtos and entities. Once in a while you may find a need for a dto but it shouldn’t be routine. For web applications that use JSP or thymeleaf you may be able to happily add entities as request attributes and let the template use them directly. If your controllers need to return JSON, you may be able to hook up a messageconverter to generate JSON directly from the entity or it may be better to have a dto. Try to keep the focus on implementing business functionality and avoid moving data from one kind of holder into a different kind of holder, because that kind of code is brittle and error-prone.

For differences between controllers and services I have answers here and here.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • Nice explanation. Actually I have a Service class that is loading five repositories (loaded in the constructor). Does it cause any problem? My doubt is coming from the rule of "don't have too many arguments in each method's signature". I've added all the repositories involved into the operation, which needs to get the data from different entities. I think that in this way the transaction will be safe. If there's a better method to do this I'm all ears. – funder7 Apr 24 '20 at 14:35
  • @Funder: i don't think that in itself constitutes a problem. maybe you could have nested services that propagate the transaction if it seems warranted. but i've seen a lot worse. :-) – Nathan Hughes Apr 24 '20 at 14:38
  • Huges, Well I'm reading about BPM tools (like jBPM or Camunda), I'm always worried about not implementing exagerated external utilities, but I think that maybe there's something useful in bpm. I admit that I'm coming back to java after 2 years, so I feel like I don't know a lot of things. Let's experiment :-) – funder7 Apr 24 '20 at 15:15
15

Controller Ideally controllers are only meant to accept HTTP requests and providing responses. A controller shouldn't bother if the data is being saved in a database or a file or sent to another service with another HTTP call. Always keep controllers free from any business thing.

Service

Service layer is a place where your transform your Data Transfer objects to Entities and vice versa. Controllers receive Data Objects which may/may not be mapped directly to the Database entities. This transformation is done by service layer. In your case your controller receives Registration

Hence best way is to let your controller just call RegistrationService and pass it the received data object to process it.

It's now registration service's job to transform data objects into database entities and save them in a transaction.


Should/Can I use other services in a service? Or I have to use only repositories in a service?

I prefer keeping the concerns separate.

e.g Only the AccountsService should know in and outs of Accounts Repository. Account Service should act like a middleman for dealing with accounts. You want to save it? give it to me. You want to find it? I will find for you.

In short, I will prefer RegistrationService calling Other services instead of repositories directly.

Amit Phaltankar
  • 3,341
  • 2
  • 20
  • 37
  • 1
    I agree with u, that use the aggregate service to contains other services to complete a complex logic. But make sure the dependencies between services. – Troy Young Aug 23 '18 at 01:31
  • If you do that you will need to repeat the function of repository in the domain service – pham cuong Nov 27 '19 at 09:05
  • I'm not sure about this approach, because of the transaction boundaries. I would prefer to have one registration transaction, not multiple. – Dragoslav Petrovic Feb 22 '21 at 19:51
  • @DragoslavPetrovic I don't think it is an issue. I have this situation right now and my service has dependency on another service. Both of which call their repositories to persist data in transactional way. You simply annotate service with Transactional annotation and it will span over both calls to two repositories (& 2 services) Another way to achieve same is using JPA/hibernate cascading if data is somehow related in 1:m , 1:1, ... relations – pixel Jun 12 '22 at 01:08