Sobre matamata

Enunciado

  • Pequeno sistema e API HTTP

  • Sistema de torneio por chaves eliminatórias

Regras

  • Pareamento inicial

  • Cada rodada

  • Rodada final

Pareamento inicial

  • Competidores divididos em duplas de forma aleatória

  • Caso não pareie todos os competidores, alguns passam automaticamente para a segunda rodada

Cada rodada

  • Competem entre si

  • Vencedores para a próxima rodada e competem contra outros vencedores

Rodada final

  • Vencedores da penúltima rodada: partida final com vencedor do torneio e segundo lugar

  • Perdedores da penúltima rodada: partida de terceira lugar com terceiro e o quarto lugar

Racional do Desenvolvimento

máquina de estados do racional

Versão de Referência

Documentação

  1. Método de Documentação

  2. Linguagem Ubíqua

  3. Regras Estabelecidas

  4. Mapeamento de Dados

  5. API e Endpoints

Architecture Decision Record

exemplo de ADR através do ADR 1 do projeto

ADR

título e data do ADR2

título e data do ADR3

título e data do ADR4

ADR

título e data do ADR5

título e data do ADR6

Linguagem Ubíqua

Competidor (Competitor)

Um sujeito (indivíduo, time, etc.) que compete em zero ou mais torneios

Torneio (Tournament)

Usado ubiquamente como representação de torneio de eliminação única

Partida (Match)

Confronto de dois competidores, com um resultado de um vencedor e um perdedor. É possível casos em que possua somente um competidor e por padrão tal competidor é o vencedor

Linguagem Ubíqua

Usuário (User)

Um cliente que interage com o sistema de gerenciamento usando ciclos requisição-resposta de API REST

Regras Estabelecidas

Foram definidas 14 regras sendo as mais importantes:

  • Um torneio deve ter ao menos um competidor para a criação de suas partidas

  • O sistema é responsável pela geração aleatória inicial das partidas e das rodadas seguindo as regras

  • Uma partida deve ser criada com um competidor ou duas partidas anteriores

Ressalvas

Tão importante quanto as regras são as ressalvas

  • Um competidor não pode desistir do torneio

  • Não registraremos a pontuação da partida, somente o vencedor e o perdedor

  • O sistema não permite empate entre os competidores

Ciclo de Vida da Partida

ciclo de vida da partida

Ciclo de Vida do Torneio

ciclo de vida do torneio

Diagrama Entidade Relacionamento

diagrama entidade relacionamento

Tabelas do Banco de Dados Relacional

diagrama entidade relacionamento do banco relacional

Campos de identificação

id

Inteiro para indexação das instâncias

uuid

Campo UUID para exposição externa da instância

label

Representação textual da instância

Carimbo de tempo

created

Criação da instância

updated

Última atualização da instância

Tournament.matchesCreation

Criação das partidas do torneio

Matches.resultRegistration

Registro do resultado de partida

Dados de Torneio

competitors

Inteiro positivo representando quantidade de competidores durante o início do torneio

startingRound

Inteiro não-negativo representando a rodada de início do torneio

Dados de Partida

round

Inteiro não-negativo representando quantidade de rodadas até a partida final

position

Inteiro não-negativo representando a posição da partida na rodada do torneio

Dados de Partida

competitorA

Referência para um competidor, aqui chamado de Competidor A

competitorB

Referência para um competidor, aqui chamado de Competidor B

winner

Referência para o vencedor

loser

Referência para o perdedor

Dados de Torneio e Competidor

nextMatch

Referência a próxima partida do competidor no torneio

API e Endpoints

Dividido em

  • Escrita

  • Leitura

API e Endpoints de Escrita

Proposta inicial

Cadastro de novos torneios

POST /tournament

Cadastro dos competidores

POST /tournament/<t_id>/competitor

Cadastro de resultado de partida

POST /tournament/<t_id>/match/<m_id>

API e Endpoints de Leitura

Proposta inicial

Listagem de partidas

GET /tournament/<t_id>/match

Exibição do TOP 4

GET /tournament/<t_id>/result

API e Endpoints de Escrita (modificado)

Proposta inicial

Proposta final

Cadastro de novos competidores

POST /competitor

Cadastro de novos torneios

POST /tournament

POST /tournament

Cadastro dos competidores

POST /tournament/<t_id>/competitor

POST /tournament/<t_uuid>/competitor

API e Endpoints de Escrita (modificado)

Proposta inicial

Proposta final

Início do torneio

POST /tournament/<t_uuid>/start

Cadastro de resultado de partida

POST /tournament/<t_id>/match/<m_id>

POST /match/<m_uuid>

API e Endpoints de Leitura (modificado)

Proposta inicial

Proposta final

Listagem de partidas

GET /tournament/<t_id>/match

GET /tournament/<t_uuid>/match

Exibição do TOP 4

GET /tournament/<t_id>/result

GET /tournament/<t_uuid>/result

POST /competitor

Status code: 201 Created

Payload

Response

{
  "label": "South Korea"
}
{
  "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
  "label": "South Korea"
}

POST /tournament

Status code: 201 Created

Payload

Response

{
  "label": "2002 FIFA World Cup"
}
{
  "uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
  "label": "2002 FIFA World Cup"
}

POST /tournament/<t_uuid>/competitor

Status code: 201 Created

Payload

Response

{
  "competitor_uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9"
}
{
  "tournament": {
    "uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
    "label": "2002 FIFA World Cup"
  },
  "competitor": {
    "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
    "label": "South Korea"
  }
}

POST /tournament/<t_uuid>/competitor

Falhas

404 Not Found

Competidor inexistente

404 Not Found

Torneio inexistente

409 Conflict

Competidor já registrado no torneio

409 Conflict

Torneio já iniciado não permitindo novos registros

POST /tournament/<t_uuid>/start

Status code: 201 Created

Payload: N/A

POST /tournament/<t_uuid>/start

Response

{
  "tournament": {
    "uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
    "label": "2002 FIFA World Cup",
    "startingRound": 1,
    "numberCompetitors": 4
  },
  "competitors": [
    {
      "uuid": "de686e37-804b-4815-a507-d5879a240af6",
      "label": "Germany"
    },
    {
      "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
      "label": "South Korea"
    },
    {
      "uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
      "label": "Brazil"
    },
    {
      "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
      "label": "Turkey"
    }
  ],
  "matches": [
    {
      "uuid": "1e172084-ec76-4f56-bd8e-7b3c170e1221",
      "round": 1,
      "position": 0,
      "competitorA": {
        "uuid": "de686e37-804b-4815-a507-d5879a240af6",
        "label": "Germany"
      },
      "competitorB": {
        "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
        "label": "South Korea"
      },
      "winner": null,
      "loser": null
    },
    {
      "uuid": "3866cad6-ba40-44fb-96c6-09f1131c5649",
      "round": 1,
      "position": 1,
      "competitorA": {
        "uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
        "label": "Brazil"
      },
      "competitorB": {
        "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
        "label": "Turkey"
      },
      "winner": null,
      "loser": null
    },
    {
      "uuid": "1f1fc156-4382-427c-aefb-5ae10009b7ce",
      "round": 0,
      "position": 0,
      "competitorA": null,
      "competitorB": null,
      "winner": null,
      "loser": null
    },
    {
      "uuid": "a9367a16-3f64-408b-9596-4029f7f60e62",
      "round": 0,
      "position": 1,
      "competitorA": null,
      "competitorB": null,
      "winner": null,
      "loser": null
    }
  ]
}

POST /tournament/<t_uuid>/start

Falhas

404 Not Found

Torneio inexistente

422 Unprocessable Content

Torneio não possui um competidor registrado

409 Conflict

Torneio já criou suas partidas

POST /match/<m_uuid>

Status code: 200 OK

Payload:

{
  "winner_uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e"
}

POST /match/<m_uuid>

Response

{
  "uuid": "a9367a16-3f64-408b-9596-4029f7f60e62",
  "tournament": {
    "uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
    "label": "2002 FIFA World Cup",
    "startingRound": 1,
    "numberCompetitors": 4
  },
  "round": 0,
  "position": 0,
  "competitorA": {
    "uuid": "de686e37-804b-4815-a507-d5879a240af6",
    "label": "Germany"
  },
  "competitorB": {
    "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
    "label": "Brazil"
  },
  "winner": {
    "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
    "label": "Brazil"
  },
  "loser": {
    "uuid": "de686e37-804b-4815-a507-d5879a240af6",
    "label": "Germany"
  }
}

POST /match/<m_uuid>

Falhas

404 Not Found

Partida inexistente

409 Conflict

Partida já registrou seu resultado

422 Unprocessable Content

Partida não está pronta para registrar seu resultado devido a partidas anteriores com competidor faltante

GET /tournament/<t_uuid>/match

Status code: 200 OK

Payload: N/A

GET /tournament/<t_uuid>/match

Response

{
  "tournament": {
    "uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
    "label": "2002 FIFA World Cup",
    "startingRound": 1,
    "numberCompetitors": 4
  },
  "past": [
    {
      "uuid": "1e172084-ec76-4f56-bd8e-7b3c170e1221",
      "round": 1,
      "position": 0,
      "competitorA": {
        "uuid": "de686e37-804b-4815-a507-d5879a240af6",
        "label": "Germany"
      },
      "competitorB": {
        "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
        "label": "South Korea"
      },
      "winner": {
        "uuid": "de686e37-804b-4815-a507-d5879a240af6",
        "label": "Germany"
      },
      "loser": {
        "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
        "label": "South Korea"
      }
    },
    {
      "uuid": "3866cad6-ba40-44fb-96c6-09f1131c5649",
      "round": 1,
      "position": 1,
      "competitorA": {
        "uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
        "label": "Brazil"
      },
      "competitorB": {
        "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
        "label": "Turkey"
      },
      "winner": {
        "uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
        "label": "Brazil"
      },
      "loser": {
        "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
        "label": "Turkey"
      }
    },
    {
      "uuid": "a9367a16-3f64-408b-9596-4029f7f60e62",
      "round": 0,
      "position": 1,
      "competitorA": {
        "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
        "label": "South Korea"
      },
      "competitorB": {
        "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
        "label": "Turkey"
      },
      "winner": {
        "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
        "label": "Turkey"
      },
      "loser": {
        "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
        "label": "South Korea"
      }
    }
  ],
  "upcoming": [
    {
      "uuid": "1f1fc156-4382-427c-aefb-5ae10009b7ce",
      "round": 0,
      "position": 0,
      "competitorA": {
        "uuid": "de686e37-804b-4815-a507-d5879a240af6",
        "label": "Germany"
      },
      "competitorB": {
        "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
        "label": "Brazil"
      },
      "winner": null,
      "loser": null
    }
  ]
}

GET /tournament/<t_uuid>/match

Falhas

404 Not Found

Torneio inexistente

422 Unprocessable Content

Torneio ainda não criou suas partidas

GET /tournament/<t_uuid>/result

Status code: 200 OK

Payload: N/A

GET /tournament/<t_uuid>/result

Response

{
  "tournament": {
    "uuid": "03c964f8-7f5c-4224-b848-1ab6c1413c7d",
    "label": "2002 FIFA World Cup",
    "startingRound": 1,
    "numberCompetitors": 4
  },
  "top4": [
    {
      "uuid": "7f026276-0904-4a7b-ae14-8c66b95ffc9e",
      "label": "Brazil"
    },
    {
      "uuid": "de686e37-804b-4815-a507-d5879a240af6",
      "label": "Germany"
    },
    {
      "uuid": "15f4fe33-f317-4c4a-96e0-3b815dc481c6",
      "label": "Turkey"
    },
    {
      "uuid": "5d1bd1d1-2679-432a-ac11-ebfebfa1bce9",
      "label": "South Korea"
    }
  ]
}

GET /tournament/<t_uuid>/result

Falhas

404 Not Found

Torneio inexistente

422 Unprocessable Content

Torneio ainda não criou suas partidas

422 Unprocessable Content

Torneio ainda não está pronto para exibir seus Top 4 competidores

Implementação

  • CPython 3.12

  • uvicorn

  • FastAPI (pydantic + Starlette) + pydantic-settings

  • SQLAlchemy + SQLAlchemy-Utils + Alembic

  • pytest + pytest-cov + factory_boy

Ordem de Desenvolvimento

  • Modelos SQLAlchemy

  • Modelos pydantic

  • Rotas FastAPI

  • Serviços

Modelo SQLAlchemy Competitor

  • CHECK Constraint

    • NOT(TRIM(label) LIKE '')

Modelo SQLAlchemy Tournament

  • CHECK Constraint

    • NOT(TRIM(label) LIKE '')

    • (matchesCreation IS NULL AND numberCompetitors IS NULL AND startingRound IS NULL) OR (matchesCreation IS NOT NULL AND numberCompetitors IS NOT NULL AND startingRound IS NOT NULL AND numberCompetitors >= 1 AND startingRound >= 0)

Modelo SQLAlchemy TournamentCompetitor

  • UNIQUE Constraint

    • tournament_id, competitor_id

Modelo SQLAlchemy Match

  • UNIQUE Constraint

    • tournament_id, round, position

  • CHECK Constraint

    • round >= 0

    • position >= 0

    • (round == 0 AND position < 2) OR (round > 0 AND position < pow(2, round))

Modelo SQLAlchemy Match

  • CHECK Constraint

    • (competitorA_id IS NULL AND competitorB_id IS NULL) OR (competitorA_id <> competitorB_id)

    • (resultRegistration IS NULL AND winner_id is NULL) OR (resultRegistration IS NOT NULL AND winner_id IS NOT NULL)

    • (resultRegistration IS NULL AND loser_id is NULL) OR (resultRegistration IS NOT NULL AND loser_id is NULL) OR (resultRegistration IS NOT NULL AND loser_id IS NOT NULL)

Modelos pydantic de Payload

  • CompetitorPayloadSchema (label)

  • TournamentPayloadSchema (label)

  • TournamentCompetitorPayloadSchema (competitor_uuid)

  • WinnerPayloadSchema (winner_uuid)

Modelos pydantic de Response

  • CompetitorSchema (label e uuid)

  • TournamentSchema (label e uuid)

  • TournamentCompetitorSchema (tournament e competitor)

  • TournamentStartSchema (tournament, competitor e matches)

  • TournamentMatchesSchema (tournament, past e upcoming)

  • TournamentResultSchema (tournament e top4)

  • MatchSchema (uuid, tournament, round, position, competitorA, competitorB, winner e loser)

Serviços

  • start_tournament

  • register_match_result

Parâmetros do Torneio

Exceto para o caso de um único competidor,

seja \$C\$ o número de competitores registrados,

seja \$R\$ o número da rodada inicial, e

seja \$M\$ o número de partidas de entrada.

Temos que

\$R = \lfloor\log_{2}(C-1)\rfloor\$

\$M = 2^{R}\$

start_tournament

  • Calcula parâmetros do torneio (competidores, rodada de entrada, quantidade de partidas de entrada)

  • Prepara dados de partida como lista de dicionários

  • Prepara sequência aleatória de competidores

  • Aloca competidores nas partidas de entrada

  • Calcula as partidas automaticamente ganhas e ajusta as seguintes

  • Insere partidas em lote no banco de dados

  • Ajusta as referências de próximas partidas

Torneio de 5 competidores

Parâmetros do torneio

\$C = 5\$

\$R = \lfloor\log_{2}(C-1)\rfloor = 2\$

\$M = 2^{R} = 4\$

Torneio de 5 competidores

Montagem de dados de partida com lista de dicionários:

r2p0

r2p1

r2p2

r2p3

r2p0

r2p1

r2p2

r2p3

r1p0

r1p1

r2p0

r2p1

r2p2

r2p3

r1p0

r1p1

r0p0

r2p0

r2p1

r2p2

r2p3

r1p0

r1p1

r0p0

r0p1

Torneio de 5 competidores

Antes de sequência aleatória:

A

B

C

D

E

Após definição de sequência aleatória:

B

C

A

E

D

Torneio de 5 competidores

Aloca competidores nas partidas de entrada

Competidores

B

C

A

E

D

Rodada 2 (quartas de final)

r2p0

cA null

cB null

r2p1

cA null

cB null

r2p2

cA null

cB null

r2p3

cA null

cB null

Torneio de 5 competidores

Aloca competidores nas partidas de entrada

Competidores

B

C

A

E

D

Rodada 2 (quartas de final)

r2p0

cA B

cB null

r2p1

cA null

cB null

r2p2

cA null

cB null

r2p3

cA null

cB null

Torneio de 5 competidores

Aloca competidores nas partidas de entrada

Competidores

B

C

A

E

D

Rodada 2 (quartas de final)

r2p0

cA B

cB null

r2p1

cA C

cB null

r2p2

cA null

cB null

r2p3

cA null

cB null

Torneio de 5 competidores

Aloca competidores nas partidas de entrada

Competidores

B

C

A

E

D

Rodada 2 (quartas de final)

r2p0

cA B

cB null

r2p1

cA C

cB null

r2p2

cA A

cB null

r2p3

cA null

cB null

Torneio de 5 competidores

Aloca competidores nas partidas de entrada

Competidores

B

C

A

E

D

Rodada 2 (quartas de final)

r2p0

cA B

cB null

r2p1

cA C

cB null

r2p2

cA A

cB null

r2p3

cA E

cB null

Torneio de 5 competidores

Aloca competidores nas partidas de entrada

Competidores

B

C

A

E

D

Rodada 2 (quartas de final)

r2p0

cA B

cB D

r2p1

cA C

cB null

r2p2

cA A

cB null

r2p3

cA E

cB null

Torneio de 5 competidores

Aloca competidores nas partidas de entrada

Competidores

B

C

A

E

D

Rodada 2 (quartas de final)

r2p0

cA B

cB D

r2p1

cA C

cB null

r2p2

cA A

cB null

r2p3

cA E

cB null

Torneio de 5 competidores

Ajusta partidas seguintes às automaticamente ganhas

Rodada 1 (semifinal)

r1p0

cA null

cB C

r1p1

cA A

cB E

register_match_result

  • Valida a partida para registrar o resultado

  • Ajusta as próximas partidas

    • Atualiza os dados de próxima partida do vencedor

    • Atualiza os dados de próxima partida do perdedor

Torneio de 3 competidores

Rodada 1 (semifinal)

r1p0

cA B

cB C

r1p1

cA A

cB null

Rodada 0 (final e terceiro lugar)

r0p0

cA null

cB A

r0p1

cA null

cB null

Torneio de 3 competidores

Competitor C ganhou r1p0

Rodada 0 (final e terceiro lugar)

r0p0

cA C

cB A

r0p1

cA B

cB null

winner B

OpenAPI

OpenAPI para versão de referência

pytest-cov

Name                                           Stmts   Miss  Cover
------------------------------------------------------------------
src/matamata/__init__.py                           1      0   100%
src/matamata/database.py                           7      2    71%
src/matamata/main.py                               7      0   100%
src/matamata/models/__init__.py                    5      0   100%
src/matamata/models/base.py                       14      0   100%
src/matamata/models/competitor.py                 13      0   100%
src/matamata/models/constants.py                  10      0   100%
src/matamata/models/exceptions.py                  4      0   100%
src/matamata/models/match.py                      25      0   100%
src/matamata/models/tournament.py                 29      0   100%
src/matamata/models/tournament_competitor.py      14      0   100%
src/matamata/routers/__init__.py                   0      0   100%
src/matamata/routers/competitor.py                13      0   100%
src/matamata/routers/match.py                     70      0   100%
src/matamata/routers/tournament.py                83      0   100%
src/matamata/schemas.py                           62      2    97%
src/matamata/services.py                          81      2    98%
src/matamata/settings.py                           5      0   100%
------------------------------------------------------------------
TOTAL                                            443      6    99%

Melhorias Feitas

  • Refatoração para melhorar legibilidade

Melhorias Feitas

  • Endpoints para melhorar usabilidade

título e data do ADR7

Endpoints para melhorar usabilidade

Endpoint

Listagem de competidores

GET /competitor

Detalhe de competidor com torneios passados, em andamento e futuros

GET /competitor/<c_uuid>

Detalhe de partida

GET /match/<m_uuid>

Endpoints para melhorar usabilidade

Endpoint

Listagem de torneios

GET /tournament

Listagem de competidores em um torneio

GET /tournament/<t_uuid>/competitor

Listagem de todas as partidas de um competidor em um torneio dividido em passadas e futuras

GET /competitor/<c_uuid>

OpenAPI após Melhorias Feitas

OpenAPI para versão com melhorias

Melhorias Feitas

  • Mudança de SQLite para PostgreSQL para melhorar escalabilidade

título e data do ADR8

Melhorias Feitas

  • Substituição de venv local para o uso de Docker Compose com compartilhamento de arquivos locais

pytest-cov após Melhorias Feitas

 Name                                             Stmts   Miss  Cover
 --------------------------------------------------------------------
 src/matamata/__init__.py                             1      0   100%
 src/matamata/database.py                             7      2    71%
 src/matamata/main.py                                 7      0   100%
 src/matamata/models/__init__.py                      5      0   100%
 src/matamata/models/base.py                         14      0   100%
 src/matamata/models/competitor.py                   16      0   100%
 src/matamata/models/constants.py                    10      0   100%
 src/matamata/models/exceptions.py                    4      0   100%
 src/matamata/models/match.py                        25      0   100%
 src/matamata/models/tournament.py                   31      0   100%
 src/matamata/models/tournament_competitor.py        15      0   100%
 src/matamata/routers/__init__.py                     0      0   100%
 src/matamata/routers/competitor.py                  34      0   100%
 src/matamata/routers/match.py                       32      0   100%
 src/matamata/routers/tournament.py                 124      0   100%
 src/matamata/schemas.py                             94      2    98%
 src/matamata/services/__init__.py                    2      0   100%
 src/matamata/services/exceptions.py                 10      0   100%
 src/matamata/services/register_match_result.py      86      0   100%
 src/matamata/services/start_tournament.py           93      2    98%
 src/matamata/settings.py                             5      0   100%
 --------------------------------------------------------------------
 TOTAL                                              615      6    99%

Alexandre Harano

/ @ayharano

email@ayharano.dev