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

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

Условие задачи:
Необходимо спроектировать и реализовать REST API для работы с договорами.

API должно поддерживать:

  • создание нового договора;

  • получение договора по его уникальному номеру.

Код:

public class Contract {

    private Long id;
    private String number;
    private String clientId;
    private String status;
    private Instant createdAt;

    // getters / setters / constructors
}

Спойлеры к решению

Подсказки
💡 Для создания договора используй POST /api/contracts.
💡 Для получения по номеру — GET /api/contracts/{number}.
💡 Входные данные лучше принимать через отдельный DTO.
💡 Номер договора должен быть уникальным.
💡 Для отсутствующего договора возвращай 404, для дубликата номера — 409.

Решение

DTO для создания договора:

public record ContractCreateDto(
        @NotBlank String number,
        @NotBlank String clientId
) {
}

DTO ответа:

public record ContractResponseDto(
        Long id,
        String number,
        String clientId,
        String status,
        Instant createdAt
) {
}

Сервис с хранением данных в памяти:

@Service
public class ContractService {

    private final Map<String, Contract> contracts =
            new ConcurrentHashMap<>();

    private final AtomicLong idGenerator =
            new AtomicLong();

    public Contract create(ContractCreateDto request) {
        Contract contract = new Contract(
                idGenerator.incrementAndGet(),
                request.number(),
                request.clientId(),
                "NEW",
                Instant.now()
        );

        Contract existing = contracts.putIfAbsent(
                contract.getNumber(),
                contract
        );

        if (existing != null) {
            throw new ContractAlreadyExistsException(
                    request.number()
            );
        }

        return contract;
    }

    public Contract getByNumber(String number) {
        Contract contract = contracts.get(number);

        if (contract == null) {
            throw new ContractNotFoundException(number);
        }

        return contract;
    }
}

Контроллер:

@RestController
@RequestMapping("/api/contracts")
public class ContractController {

    private final ContractService contractService;

    public ContractController(
            ContractService contractService
    ) {
        this.contractService = contractService;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ContractResponseDto create(
            @Valid @RequestBody ContractCreateDto request
    ) {
        return toResponse(
                contractService.create(request)
        );
    }

    @GetMapping("/{number}")
    public ContractResponseDto getByNumber(
            @PathVariable String number
    ) {
        return toResponse(
                contractService.getByNumber(number)
        );
    }

    private ContractResponseDto toResponse(
            Contract contract
    ) {
        return new ContractResponseDto(
                contract.getId(),
                contract.getNumber(),
                contract.getClientId(),
                contract.getStatus(),
                contract.getCreatedAt()
        );
    }
}

Обработка ошибок:

@RestControllerAdvice
public class ContractExceptionHandler {

    @ExceptionHandler(ContractNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handleNotFound(
            ContractNotFoundException exception
    ) {
        return exception.getMessage();
    }

    @ExceptionHandler(ContractAlreadyExistsException.class)
    @ResponseStatus(HttpStatus.CONFLICT)
    public String handleConflict(
            ContractAlreadyExistsException exception
    ) {
        return exception.getMessage();
    }
}

Получившийся API:

POST /api/contracts
GET  /api/contracts/{number}

Создание договора возвращает 201 Created, получение существующего договора — 200 OK, отсутствие договора — 404 Not Found, попытка создать договор с уже существующим номером — 409 Conflict.

При использовании PostgreSQL уникальность номера дополнительно должна обеспечиваться UNIQUE-ограничением в базе данных.