Спроектировать REST API для управления пользователем и его атрибутами

8. Спроектировать и реализовать REST API для управления пользователем

Описание
Нужно спроектировать и реализовать REST API для управления пользователями:

  • создать пользователя
  • получить пользователя по id
  • получить список пользователей
  • обновить пользователя
  • удалить пользователя

В рамках задания используем Spring Boot (без БД можно в памяти, но можно легко заменить на JPA).

Модель User:

public class User {
    private Long id;
    private String name;
    private String email;
    private Integer age;

    // getters/setters/constructors
}
Спойлеры к решению
Подсказки
💡 Для REST API обычно делают @RestController и маппинги POST/GET/PUT/DELETE.
💡 Валидацию DTO удобно делать через @Valid + @NotBlank/@Email/@Min.
💡 Хранилище можно сделать на ConcurrentHashMap<Long, User> + генерация id через AtomicLong.
💡 Для ошибок хорошо вернуть 404 если не нашли, и 400 если валидация не прошла.
💡 Для ручных тестов удобно подготовить .http файлы (IDEA умеет выполнять).
Решение

Ниже пример простого решения “в памяти” (без БД), но с корректными HTTP-статусами и DTO-валидацией.

  1. DTO + модель
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;

public class UserDto {
    @NotBlank
    private String name;

    @Email
    @NotBlank
    private String email;

    @Min(0)
    private Integer age;

    // getters/setters
}
public class User {
    private Long id;
    private String name;
    private String email;
    private Integer age;

    public User() {}

    public User(Long id, String name, String email, Integer age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }

    // getters/setters
}
  1. Service (в памяти)
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class UserService {
    private final Map<Long, User> storage = new ConcurrentHashMap<>();
    private final AtomicLong idSeq = new AtomicLong(0);

    public User create(UserDto dto) {
        long id = idSeq.incrementAndGet();
        User user = new User(id, dto.getName(), dto.getEmail(), dto.getAge());
        storage.put(id, user);
        return user;
    }

    public User getById(long id) {
        User user = storage.get(id);
        if (user == null) {
            throw new NoSuchElementException("User not found: " + id);
        }
        return user;
    }

    public List<User> getAll() {
        return storage.values().stream()
                .sorted(Comparator.comparing(User::getId))
                .toList();
    }

    public User update(long id, UserDto dto) {
        User existing = getById(id);
        existing.setName(dto.getName());
        existing.setEmail(dto.getEmail());
        existing.setAge(dto.getAge());
        storage.put(id, existing);
        return existing;
    }

    public void delete(long id) {
        if (storage.remove(id) == null) {
            throw new NoSuchElementException("User not found: " + id);
        }
    }
}
  1. Controller
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.NoSuchElementException;

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

    private final UserService service;

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

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public User create(@Valid @RequestBody UserDto dto) {
        return service.create(dto);
    }

    @GetMapping("/{id}")
    public User getById(@PathVariable long id) {
        return service.getById(id);
    }

    @GetMapping
    public List<User> getAll() {
        return service.getAll();
    }

    @PutMapping("/{id}")
    public User update(@PathVariable long id, @Valid @RequestBody UserDto dto) {
        return service.update(id, dto);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void delete(@PathVariable long id) {
        service.delete(id);
    }

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoSuchElementException.class)
    public String handleNotFound(NoSuchElementException e) {
        return e.getMessage();
    }
}
  1. .http файлы для ручных тестов API

users-create.http

POST http://localhost:8080/api/users
Content-Type: application/json

{
  "name": "Alice",
  "email": "alice@mail.com",
  "age": 25
}

users-get.http

GET http://localhost:8080/api/users/1

GET http://localhost:8080/api/users

users-update.http

PUT http://localhost:8080/api/users/1
Content-Type: application/json

{
  "name": "Alice Updated",
  "email": "alice.updated@mail.com",
  "age": 26
}

users-delete.http

DELETE http://localhost:8080/api/users/1