Projeto relacionado à disciplina Prática em Desenvolvimento de Software 2022/01 da UFMG
Proposta
Inspirado em plataformas como o bet365 e o sportsBet, a equipe desenvolverá um site que permite aos seus usuários realizarem apostas online. O nosso site permitirá o cadastro de cenários de aposta (e.g.: cadastrar um cenário referente a um jogo entre Cruzeiro e Atlético) e registrar as apostas de usuários (e.g: apostar $50 que o Cruzeiro será vencedor). Além disso, cuidará do cálculo das chances e prêmios envolvidos nas apostas.
O site não realizará sorteios. Ele apenas permitirá apostas.
- Bárbara Ribeiro (@barbaragribeiro) - Desenvolvedora Fullstack
- Jean George (@jeangeorge) - Desenvolvedor Fullstack
- João Pedro Renan (@jotarenan) - Desenvolvedor Fullstack
- Luiz Berto (@luiz787) - Desenvolvedor Fullstack
- Backend: Python, usando o framework Django
- Frontend: Typescript, usando a biblioteca React
- Banco de Dados: MySQL
- Containerização: Docker com Docker Compose
O MVP do BETinho visa validar se é possível e se há interesse em utilizar o meio virtual para se realizar apostas. A nossa hipótese é de que essa demanda existe, pois apostas são uma forma de diversão e entretenimento constante na sociedade, e formas de acesso ao ambiente virtual tornaram-se ubíquas.
Para isso, tomaremos as principais características encontradas em sistemas de apostas tradicionais e as reproduziremos em um site. Os tradicionais clubes de apostas em partidas esportivas, como futebol e corridas de cavalos, são a nossa principal referência.
De forma simplificada, o nosso MVP permitirá aos usuários apostarem créditos virtuais no que acreditam que será o resultado de um evento. Ele irá calcular, com base no volume de apostas, qual é o resultado mais esperado pelos apostadores e balanceará o prêmio de acordo com isso. Em outras palavras, o BETinho buscará premiar melhor aqueles que correrem mais risco.
Por exemplo: imagine que o evento seja um jogo do Atlético contra Cruzeiro. Se a maioria das apostas indicar uma vitória do Atlético, aqueles que apostarem no time receberão um prêmio menor caso este ganhe do que aqueles que apostarem no Cruzeiro, caso a vitória seja cruzeirense.
Front-end
- Gerar Dockerfile para o front-end e incluir no docker-compose (Jean)
- Gerar projeto base React (João Pedro)
- Instalar e configurar extensões do VSCode (João Pedro)
Back-end
- Configurar Dockerfile de Python e incluir no docker-compose (Bárbara)
- Configurar django (Bárbara)
Banco de dados
- Incluir MySQL no docker-compose (Luiz)
- Como usuário do BETinho
- Quero ver a listagem dos eventos disponíveis para aposta
- Para verificar eventos de interesse e escolher eventos para realizar apostas.
- Como usuário do BETinho
- Quero poder visualizar detalhes de um evento específico como data, hora, local, participantes envolvidos e odds
- Para me informar acerca do evento, saber qual o retorno potencial de uma aposta e decidir se vou ou não apostar (e em qual resultado).
- Como usuário do BETinho
- Quero poder apostar em um resultado de um evento
- Para obter retorno financeiro fictício caso o resultado se concretize.
- Como administrador do BETinho
- Quero poder cadastrar novos eventos
- Para permitir que os usuários do BETinho consigam fazer apostas nesses eventos.
- Como administrador do BETinho
- Quero poder editar e deletar eventos
- Para poder adaptar a plataforma à mudanças externas (ex.: cancelamento de jogo, mudança de horário), e dessa manter a qualidade do conteúdo do BETinho.
- Como administrador do BETinho
- Quero que o sistema calcule automaticamente as odds de cada possível resultado de um evento, baseado na proporção de apostas em cada resultado
- Para que os usuários saibam o potencial de ganho em cada possível resultado de um evento, e para que isso não tenha que ser feito manualmente pelos administradores do BETinho.
- Como administrador do BETinho
- Quero que o sistema permita lançar o resultado de um evento
- Para que o evento seja encerrado e o pagamento para os vencedores possa ser feito.
- Como usuário do BETinho
- Quero que o sistema realize o pagamento para os vencedores de forma automática quando um evento for encerrado (resultado lançado)
- Para que eu possa desfrutar dos meus gains 💪 🤑
- Como usuário do BETinho
- Quero que o sistema permita criar uma conta, editar o perfil e deletar a conta
- Para que eu possa utilizar o sistema de forma autenticada e ter controle sob meus dados.
- Como usuário do BETinho
- Quero que o sistema agrupe eventos em categorias (ex.: Futebol, Fórmula 1, Basquete)
- Para que eu possa visualizar e achar os eventos do meu interesse com maior facilidade.
- Como administrador do BETinho
- Quero que o sistema possibilite cadastrar, editar e deletar categorias
- Para que as categorias de eventos possam ser mantidas pelos administradores.
A sprint 1 contemplará as seguintes histórias do backlog do produto, subdivididas em tarefas:
- Listagem de eventos
- [Backend] Criar endpoint de listagem de todos os eventos - Bárbara
- [Frontend] Criar tela de listagem de eventos - João Pedro
- Detalhes do evento
- [Backend] Criar endpoint de obter evento por id - Bárbara
- [Frontend] Criar tela de exibição de detalhes do evento - João Pedro
- Apostar
- [Backend] Criar endpoint de criação de aposta - Luiz
- [Frontend] Criar tela de apostar - Jean
- Cadastro de eventos
- [Backend] Criar endpoint de cadastro de eventos - Bárbara
- [Frontend] Criar tela de cadastrar evento - João Pedro
- Cálculo de odds
- [Backend] Criar função de cálculo de odds - Luiz
- [Backend] Criar endpoint de leitura das odds de um evento - Luiz
- [Frontend] Criar componente para exibir odds do evento - Jean
A arquitetura foi baseada nos princípios de DDD e seguiu os moldes da arquitetura hexagonal, sendo ambos detalhados abaixo.
Os princípios do DDD permitem que o domínio da aplicação seja separado das tecnologias empregadas. Para atingir este objetivo, utilizamos uma linguagem ubíqua no código, a qual contém termos específicos ao nosso domínio como exemplificado a seguir:
- Event: Um evento envolvendo dois times, com um deles saindo vencedor;
- Team: Um time (que disputa eventos);
- Bet: Uma aposta de um valor X no time Y como vencedor de um evento Z;
- Odd: Multiplicador variável que representa o quanto uma aposta no vencedor correto paga.
Além disso, a aplicação foi construída utilizando objetos de tipos específicos alinhados com o DDD. Especificamente, utilizamos objetos de valor, entidades, serviços, repositórios e agregados.
- Objetos de valor: Estes são objetos que caracterizam um estado, que não possuem um identificador. São eles:
- datetime (da bilbioteca padrão de Python);
- EventResult, que representa o estado do resultado de um evento (casa ganha, de fora ganha, empate) e é usado no cálculo das odds
- EventRequest, que representa um pedido de criação de um evento;
- EventOdds, que contém o estado dos multiplicadores para os três possíveis resultados.
- Entidades: Entidades são objetos únicos e que possuem um identificador. Em nossa aplicação, temos Bet, Event e Team.
- Serviços: Algumas operações podem ser feitas no sistema, constituindo serviços. Implementamos os serviços EventRegistrationServiceImpl, EventFetchingServiceImpl, BetRegistrationServiceImpl, OddsFetchingServiceImpl, OddsCalculatorImpl.
- Repositórios: Implementamos alguns repositórios que têm o papel de recuperar objetos e persistir as mudanças geradas pelos serviços no banco de dados. Especificamente, temos EventRepository, BetRepository e TeamRepository.
- Agregado: Um agregado é um conjunto coerente de entidades e objetos de valor. Em nosso sistema, Event e Team formam um agregado, sendo Event a raiz.
A principal motivação para o uso da arquitetura hexagonal é manter uma separação entre domínio e tecnologia, o que se alinha aos princípios do DDD. Com isso, o baixo acoplamento não só favorece mudanças, mas também o reúso e a testabilidade do código.
Como nosso backend foi escrito usando Django, foi preciso tomar o cuidado de manter todo o framework fora da nossa camada de domínio. Essa é, inclusive, uma motivação para o uso da arquitetura hexagonal: se Django for trocado no futuro por outra tecnologia, o domínio da aplicação permanece intacto, e somente novos adaptadores serão escritos para poder se "conectar" a ele.
Nossas portas são classes abstratas (ABCs de Python, cujo papel nesse contexto é o mesmo de interfaces de outras linguagens) que os adaptadores usam para poderem se comunicar com o domínio. No caso das portas de entrada, temos:
- EventRegistrationService
- EventFetchingService
- BetRegistrationService
- OddsFetchingService
Já as portas de saída são EventRepository, BetRepository e TeamRepository. Os adaptadores, que fazem parte da camada de infraestrutura, fazem a conexão entre o domínio e tecnologias/serviços externos. No nosso caso, os adaptadores de entrada recebem requisições HTTP através do Django e chamam os serviços correspondentes do domínio. São eles:
- BetView
- EventListView
- EventRegistrationView
- OddsView
Os adaptadores de saída, por outro lado, comunicam-se com o banco de dados para buscar dados e persistir mudanças usando o ORM do Django, sendo eles BetRepositoryImpl, EventRepositoryImpl e TeamRepositoryImpl.
Um de nossos endpoints retorna as odds de determinado evento. Aqui seguiremos o fluxo que ocorre ao realizar uma chamada a esse endpoint (/events/{event_id}/odds/).
No diagrama, o hexágono laranja representa o limite do domínio, e o hexágono verde representa a camada de adaptadores. Os componentes em roxo (OddsView e BetRepositoryImpl) são os adaptadores, e os componentes em vermelho (OddsFetchingService e BetRepository) são as portas, que ficam dentro dos limites do domínio.
O fluxo começa com uma chamada HTTP, que é tratada pelo OddsView (controller, adaptador de entrada). O OddsView, por sua vez, chama a porta de entrada (OddsFetchingService), que é uma fachada para realizar uma operação no domínio, e faz parte da camada de domínio. Essa porta de entrada é implementada por uma classe de serviço de dentro do domínio, OddsFetchingServiceImpl, que orquestra a operação e se comunica com outras classes de domínio. OddsFetchingServiceImpl precisa listar as apostas de um evento para calcular as odds, então chama a porta de saída BetRepository, que é uma fachada para acesso ao banco de dados, e também reside no domínio. Essa porta de saída é implementada pelo adaptador de saída, BetRepositoryImpl, que fica na camada de adaptadores, e faz uso das facilidades do ORM do Django para realizar o acesso ao banco de dados.
Dessa forma, a camada de domínio fica livre de qualquer tipo de detalhe de tecnologia, e delimita a comunicação com o mundo externo por meio das portas e adaptadores.
Outro endpoint do nosso sistema faz o registro de um evento. Nesse caso, a chamada HTTP, do tipo post, é tratada pelo adaptador de entrada EventRegistrationView, que usa a porta de entrada EventRegistrationService para se comunicar com o domínio. O serviço que a implementa é EventRegistrationServiceImpl, o qual lida com classes do domínio para criar a nova entidade Event, e precisa se comunicar com o banco de dados para persistir a mudança, salvando o novo evento, e também para recuperar os objetos Team necessários, já que Event possui chave estrangeira para dois times, e cria um novo caso o time não esteja cadastrado ainda. Com isso, este serviço precisa usar duas portas de saída, EventRepository e TeamRepository. As duas são implementadas, respectivamente, pelos adaptadores de saída EventRepositoryImpl e TeamRepositoryImpl, os quais usam o ORM do Django para se comunicarem com o banco de dados.
Os demais endpoints (EventFetchingService e BetRegistrationService) são análogos aos dois apresentados aqui, com as portas, adaptadores e serviços explicados na seção anterior.