Skillversitas

Service Layer

Why a Service Layer?

In a Spring Boot application, the service layer acts as the business brain. The controller handles HTTP requests, the repository accesses the database, but the main business logic lives in the service layer.

Without a service layer, the controller becomes too fat and hard to maintain.

@Service Annotation

Spring provides a stereotype annotation to mark a class as a service:

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("User not found"));
    }
}

The @Service annotation is a specialization of @Component. Functionally identical, but with clearer semantic meaning.

Interface vs Implementation

Using an interface separates the contract from the implementation:

public interface UserService {
    List<User> getAllUsers();
    User getUserById(Long id);
    User createUser(User user);
}

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    @Override
    public User getUserById(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("User not found"));
    }

    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }
}

Benefits of an interface:

Benefit Description
Decoupling The controller doesn't know the implementation details
Testing Easy to create a mock implementation
Strategy swapping Change the implementation without changing the caller
Multiple impls You can have UserServiceImpl and UserMockService

Delegation from Controller to Service

A controller should only receive the request, call the service, and return the response:

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User created = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

Transactions in the Service Layer

@Transactional belongs in the service layer, because the service coordinates multiple operations:

@Service
@Transactional
public class OrderServiceImpl implements OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    public OrderServiceImpl(OrderRepository orderRepository,
                           ProductRepository productRepository) {
        this.orderRepository = orderRepository;
        this.productRepository = productRepository;
    }

    @Override
    @Transactional
    public Order createOrder(Order order) {
        Product product = productRepository.findById(order.getProductId())
            .orElseThrow(() -> new RuntimeException("Product not found"));

        if (product.getStock() < order.getQuantity()) {
            throw new RuntimeException("Insufficient stock");
        }

        product.setStock(product.getStock() - order.getQuantity());
        productRepository.save(product);

        return orderRepository.save(order);
    }

    @Override
    @Transactional(readOnly = true)
    public List<Order> getAllOrders() {
        return orderRepository.findAll();
    }
}

When @Transactional is placed at the class level, all public methods become transactional automatically. Use readOnly = true on queries for better performance.

Clean Architecture

A well-organized layering in a Spring Boot application:

Controller → Service → Repository → Database
    ↓           ↓
  DTO       Entity/Domain

The basic rules:

  1. Controller only receives requests and calls the service
  2. Service contains the business logic and transaction coordination
  3. Repository only interacts with the database
  4. Entity represents the data
  5. DTO for transferring data between layers

With a well-structured service layer, the code becomes easier to test, maintain, and extend. In the next chapter, we'll learn about the DTO Pattern for separating data representation between layers.