Reputation: 1879
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.
Upvotes: 24
Views: 20149
Reputation: 96454
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.
Upvotes: 32
Reputation: 3424
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.
Upvotes: 15