Visão geral
API de agendamento pra prestador de serviço (barbearia, salão, clínica): cada prestador define serviços e janelas de disponibilidade, o cliente vê os horários livres de verdade e agenda, e dois clientes nunca conseguem roubar o mesmo horário um do outro, mesmo tentando ao mesmo tempo.
Choque de horário é estrutural, não lógica de aplicação
A prevenção não é um SELECT seguido de INSERT com janela de corrida entre os dois. Cada
agendamento monta a própria lista de "células" de 15 minutos que ocupa na agenda do prestador,
e a chave primária dessa tabela é o par (PrestadorId, início do slot), sem Id sintético.
Fisicamente não existem duas linhas com o mesmo prestador no mesmo instante. Cancelar libera
o horário de verdade: esvazia a lista de slots, o EF Core traduz isso em DELETE.
Dois bugs de concorrência reais, dois mecanismos diferentes
Existem dois jeitos distintos de dois agendamentos colidirem. Entre duas requisições HTTP
(dois DbContext), o banco recusa o segundo INSERT com violação de índice único, traduzida
pra um erro 409 claro. Mas dois agendamentos no MESMO DbContext nunca chegam a gerar dois
INSERT: o ChangeTracker do EF Core recusa rastrear a segunda entidade com a mesma chave
antes disso, com uma exceção diferente que escapava como erro 500 até eu adicionar uma
checagem explícita no repositório.
Testes
31 testes: Domain (regras de horário, choque, virada de dia, cancelamento), Application
(SQLite real, incluindo Task.WhenAll com 6 tentativas em 3 pares de horários idênticos) e
Integration (fluxo HTTP completo via WebApplicationFactory, incluindo 8 clientes disputando
o mesmo horário ao mesmo tempo por HTTP de verdade).