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
}
Спойлеры к решению
Подсказки
💡 Для создания договора используй
💡 Для получения по номеру —
💡 Входные данные лучше принимать через отдельный DTO.
💡 Номер договора должен быть уникальным.
💡 Для отсутствующего договора возвращай
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-ограничением в базе данных.