Service Layer & Business Logic

🌿 Spring BootLesson 7Intermediate

The Service Layer sits between controllers and repositories. It contains all business logic, transaction management, and orchestration between multiple repositories. This separation keeps controllers thin and logic reusable.

1 Service Interface + Implementation
Java — UserService Interface
public interface UserService {
    Page<UserDto>   findAll(int page, int size);
    UserDto         findById(Long id);
    UserDto         create(CreateUserRequest req);
    UserDto         update(Long id, UpdateUserRequest req);
    void            delete(Long id);
    UserDto         findByEmail(String email);
}
Java — UserServiceImpl.java
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)  // Default: read-only for all methods
public class UserServiceImpl implements UserService {

    private final UserRepository  userRepository;
    private final PasswordEncoder passwordEncoder;
    private final UserMapper      userMapper;

    @Override
    public Page<UserDto> findAll(int page, int size) {
        Pageable p = PageRequest.of(page, size, Sort.by("createdAt").descending());
        return userRepository.findAll(p).map(userMapper::toDto);
    }

    @Override
    public UserDto findById(Long id) {
        return userRepository.findById(id)
            .map(userMapper::toDto)
            .orElseThrow(() -> new ResourceNotFoundException("User", id));
    }

    @Override
    @Transactional  // Override: write transaction for this method
    public UserDto create(CreateUserRequest req) {
        if (userRepository.existsByEmail(req.getEmail())) {
            throw new ConflictException("Email already registered: " + req.getEmail());
        }
        User user = userMapper.toEntity(req);
        user.setPasswordHash(passwordEncoder.encode(req.getPassword()));
        return userMapper.toDto(userRepository.save(user));
    }

    @Override
    @Transactional
    public void delete(Long id) {
        User user = userRepository.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("User", id));
        userRepository.delete(user);
    }
}
2 DTO Pattern with MapStruct
Java — UserMapper with MapStruct
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {

    @Mapping(target = "passwordHash", ignore = true)
    User toEntity(CreateUserRequest req);

    @Mapping(source = "active", target = "isActive")
    UserDto toDto(User user);

    List<UserDto> toDtoList(List<User> users);
}

// UserDto.java (Lombok)
@Data
@Builder
public class UserDto {
    private Long          id;
    private String        name;
    private String        email;
    private Role          role;
    private boolean       isActive;
    private LocalDateTime createdAt;
}
3 Code Challenge
Challenge: Build a ProductService that validates stock before allowing a purchase, updates the stock quantity atomically using @Transactional, and throws a custom InsufficientStockException if stock goes below zero.