Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Services and layers

Over the previous lessons the blog grew three kinds of classes: a controller accepts requests, a service solves the task, a repository stores data. This split is called layers. Each layer knows only about the one below it, which lets you change one part without breaking the others: in the next chapter we replace the in-memory store with a database, and the controller will not even notice.

Three layers

LayerAnnotationResponsible forMust not
Controller@RestControllerAddresses, reading the request, response codesHold domain rules
Service@ServiceLogic: checks, calculations, transactionsKnow about HTTP and ResponseEntity
Repository@RepositoryReading and writing dataDecide what may be saved

A request goes top down: controller → service → repository. The response goes back.

The whole blog service

// src/main/java/com/example/blog/PostService.java
@Service
public class PostService {
 
    private final PostRepository repository;
    private final Clock clock;
    private final BlogProperties properties;
 
    public PostService(PostRepository repository, Clock clock, BlogProperties properties) {
        this.repository = repository;
        this.clock = clock;
        this.properties = properties;
    }
 
    public List<Post> findAll(String author) {
        return repository.findAll().stream()
                .filter(post -> author == null || post.author().equals(author))
                .sorted(Comparator.comparing(Post::id))
                .limit(properties.pageSize())
                .toList();
    }
 
    public Post findById(Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new PostNotFoundException(id));
    }
 
    public Post create(NewPostRequest request) {
        Post post = new Post(null, request.title(), request.body(), request.author(), Instant.now(clock));
        return repository.save(post);
    }
 
    public Post update(Long id, NewPostRequest request) {
        Post old = findById(id);
        return repository.save(new Post(id, request.title(), request.body(), request.author(), old.createdAt()));
    }
 
    public void delete(Long id) {
        findById(id);
        repository.deleteById(id);
    }
}

Note that the service throws PostNotFoundException instead of returning a 404 code. It does not know it is called over HTTP. Turning the exception into a response is the job of the handler from the previous lesson.

Project files

src/main/java/com/example/blog/
├── BlogApplication.java
├── AppConfig.java            settings and beans
├── BlogProperties.java       a group of settings
├── PostController.java       controller layer
├── ApiExceptionHandler.java  errors → responses
├── NewPostRequest.java       input data
├── PostService.java          service layer
├── PostNotFoundException.java
├── PostRepository.java       data layer
└── Post.java                 the model

While there are few classes, one package is fine. As the project grows, classes are split into feature packages: com.example.blog.post, com.example.blog.comment, com.example.blog.user. Everything about posts sits together, with no jumping between controllers and services folders.

Checking everything at once

curl -X POST http://localhost:8080/api/posts -H "Content-Type: application/json" \
  -d '{"title":"Second","body":"Text","author":"oleg"}'
curl -X PUT http://localhost:8080/api/posts/2 -H "Content-Type: application/json" \
  -d '{"title":"Second (edited)","body":"Text","author":"oleg"}'
curl -i -X DELETE http://localhost:8080/api/posts/2

Creating returns 201, updating returns 200 with the new title, deleting returns 204. Deleting the same post again answers 404 through the error handler.

Common mistake

It is tempting to pass the repository straight into the controller and write the logic there: "find, check, save". It is shorter at first, but later the same logic is needed from a site page or a background job — and has to be copied. Keep the controller thin: take the request, call the service, return the response.

Thanks for reading!

Donate to the site — we work hard for you.