Aplicação de console (Java) que simula o fluxo completo de submissão e revisão por pares (blind-review) de artigos em um evento acadêmico: um coordenador abre o evento e monta o comitê, autores submetem artigos, o sistema distribui automaticamente os trabalhos entre os revisores conforme a área temática, e ao final os autores são notificados por e-mail com os pareceres.
Projeto da disciplina Padrões de Projeto de Software — o foco é aplicar os princípios SOLID e diversos padrões de projeto (GoF) em um cenário real.
| Campo | Informação |
|---|---|
| Curso | Sistemas para Internet |
| Disciplina | Padrões de Projeto de Software |
| Período | 5º |
| Professor | Alex Sandro da Cunha Rêgo |
- Davi
- Arthur
- Heitor
- Stack e requisitos
- Como executar
- Dados de demonstração (seed) e credenciais
- Arquitetura em camadas
- Estrutura de pastas e classes
- Padrões de projeto utilizados e onde
- Como cada requisito foi resolvido (RF01–RF10)
- Diagramas
- Java 21 (o projeto usa
records,switchde expressão e sealed-like states). - Maven (há o wrapper
mvnw/mvnw.cmd— não é preciso ter Maven instalado). - Lombok (geração de getters/setters/construtores) — já configurado no
pom.xml. - Jakarta Mail (
org.eclipse.angus:jakarta.mail) — envio real de e-mail. - dotenv-java — leitura das credenciais de e-mail a partir de um arquivo
.env.
O projeto empacota um JAR executável (via maven-shade-plugin) com classe
principal com.dah.App.
Na inicialização o programa carrega um .env; se ele não existir, a aplicação
não sobe. Basta copiar o exemplo:
# Linux / macOS
cp .env.example .env
# Windows (PowerShell)
Copy-Item .env.example .envO .env.example já vem com os campos vazios:
FROM_EMAIL = ""
EMAIL_APP_PASSWORD = ""
- Com os campos vazios (recomendado para testar/corrigir): o sistema usa um
adapter falso de e-mail (
FakeEmailSenderAdapter) que imprime os e-mails no console em vez de enviá-los. Nenhuma configuração externa é necessária. - Para enviar e-mail de verdade (Gmail): preencha
FROM_EMAILcom o remetente eEMAIL_APP_PASSWORDcom uma senha de app do Google. Aí oSmtpEmailSenderAdapterpassa a enviar de fato (SMTPsmtp.gmail.com:587, STARTTLS).
O projeto usa o Maven Wrapper, então você não precisa instalar o Maven — use o executável que já vem no repositório. O comando muda conforme o sistema operacional:
| Sistema | Wrapper | Observação |
|---|---|---|
| Linux / macOS | ./mvnw |
Script shell. Se der "permissão negada", rode antes chmod +x mvnw. |
| Windows | mvnw.cmd |
Funciona no CMD e no PowerShell (no PowerShell use .\mvnw.cmd). |
Linux / macOS (terminal bash/zsh):
./mvnw -q package
java -jar target/ppsms-1.0-SNAPSHOT.jarWindows (PowerShell):
.\mvnw.cmd -q package
java -jar target\ppsms-1.0-SNAPSHOT.jarWindows (Prompt de Comando — CMD):
mvnw.cmd -q package
java -jar target\ppsms-1.0-SNAPSHOT.jarO
-qdeixa a saída do Maven silenciosa (quiet). Na primeira execução o wrapper baixa o Maven e as dependências, então precisa de internet e pode demorar um pouco; depois fica em cache.
Se quiser apenas compilar e executar direto pela App (sem gerar o JAR), use o mesmo
wrapper do seu sistema:
# Linux / macOS
./mvnw -q compile exec:java -Dexec.mainClass=com.dah.App# Windows (PowerShell)
.\mvnw.cmd -q compile exec:java "-Dexec.mainClass=com.dah.App"Alternativa (IDE): o repositório também é um projeto Eclipse (
.project/.classpath). Basta importar e executar a classecom.dah.Appcomo Java Application. Certifique-se de ter o JDK 21 e o Lombok habilitado na IDE.
Ao iniciar, a classe DataSeeder popula automaticamente o cenário (atende ao
Requisito de Execução E1 — sem digitação inicial para testar): cria o
coordenador, alguns pesquisadores, um evento aberto (SBSI 2026, categoria
Full Paper, 2 revisores por artigo) e algumas áreas temáticas.
Contas já cadastradas para login:
| Papel | Senha | Observação | |
|---|---|---|---|
| Coordenador | coordenador@ppsms.local |
123456 |
Abre evento, cadastra áreas, convida revisores, distribui, dashboard |
| Pesquisador | arthur.silva.5@academico.ifpb.edu.br |
123 |
Autor |
| Pesquisador / Revisora | alice@ppsms.local |
123456 |
Convidada como revisora com áreas (Eng. de Software, IA) |
| Pesquisador / Revisor | bob@ppsms.local |
123456 |
Convidado como revisor; define as áreas no 1º login |
Áreas temáticas semeadas: Engenharia de Software, Inteligência Artificial e Visão Computacional.
Roteiro rápido de teste: entre como coordenador → Distribuir Artigos só tem efeito depois que houver artigos submetidos, então antes entre como autor, Submeter Artigo; volte como coordenador para Distribuir; entre como revisor (Alice/Bob) para Avaliar Artigo; e volte ao coordenador para ver o Dashboard.
O sistema é dividido em três camadas, com dependências sempre apontando "para dentro" (apresentação → lógica → dados), respeitando baixo acoplamento:
| Camada | Responsabilidade | Pacotes principais |
|---|---|---|
| Apresentação | Menus de console, leitura de entrada e exibição. Não contém regra de negócio. | presentation, presentation.input, presentation.view, command |
| Lógica (negócio) | Regras de negócio, orquestração, padrões de projeto. | facade, service, workflow, observer, domain, email |
| Dados | Persistência em memória por trás de interfaces de repositório. | repository |
Os dados não são persistidos em banco: cada repositório concreto guarda os
objetos em listas na memória (InMemory*Repository). A regra de que existe apenas
o evento atual simplifica o modelo — ao iniciar um novo evento, os dados do evento
anterior (áreas, revisores, artigos, avaliações) são descartados, enquanto os
usuários permanecem.
src/main/java/com/dah/
├── App.java # main() — ponto de entrada
├── ApplicationBootstrap.java # "montagem" (injeção de dependências manual) + loop inicial
│
├── presentation/ # CAMADA DE APRESENTAÇÃO
│ ├── MainMenu.java # roteia para o menu conforme o papel do usuário
│ ├── CoordinatorMenu.java # menu do coordenador
│ ├── ResearcherMenu.java # menu do pesquisador/revisor
│ ├── LoginFlowController.java # o que acontece após o login
│ ├── ReviewerAreaSetupScreen.java # 1º login do revisor: escolher áreas de expertise
│ ├── input/InputReader.java # leitura e validação de dados do teclado
│ └── view/ConsoleView.java # toda a saída formatada (menus, listagens, painéis)
│
├── command/ # PADRÃO COMMAND (ações do usuário)
│ ├── Command.java / NoDataCommand.java
│ ├── StartEventCommand, RegisterAreaCommand, InviteReviewerCommand,
│ ├── DistributeArticlesCommand, ShowDashboardCommand, ShowCurrentEventCommand,
│ └── SubmitArticleCommand, ReviewArticleCommand, DefineReviewerAreasCommand
│
├── facade/ # PADRÕES FACADE + PROXY
│ ├── CoordinatorActions / CoordinatorFacade
│ ├── ResearcherActions / ResearcherFacade
│ └── CoordinatorAuthorizationProxy # verifica autorização antes de delegar
│
├── service/ # SERVIÇOS DE NEGÓCIO
│ ├── AccountService # cadastro/login/usuário atual
│ ├── CommitteeService # áreas, convite de revisores, expertise
│ ├── EventService # início/reset do evento, dashboard, seleção da factory
│ ├── ArticleReviewService # submissão de artigos e submissão de pareceres
│ ├── DistributionService # distribui artigos usando a estratégia atual
│ └── DataSeeder # popula o cenário de demonstração
│
├── domain/ # ENTIDADES DE DOMÍNIO
│ ├── User, Area, Article, Review, ReviewerProfile, SubmissionEvent
│ └── article/ # PADRÃO STATE (estado do artigo)
│ ├── ArticleState (interface)
│ └── SubmittedState, UnderReview, AcceptedState, RejectedState, AttentionState
│
├── workflow/ # PADRÕES STRATEGY + ABSTRACT FACTORY
│ ├── ReviewWorkflowContext # guarda a estratégia/política ativas do evento
│ ├── factory/ # ABSTRACT FACTORY (família distribuição + política)
│ │ ├── ReviewWorkflowFactory (interface)
│ │ └── One/Two/ThreeReviewerWorkflowFactory
│ ├── distribution/ # STRATEGY de distribuição
│ │ ├── DistributionStrategy (interface) + BalancedDistributionStrategy (base comum)
│ │ └── strategies/ One/Two/ThreeReviewerDistributionStrategy
│ └── review/ # STRATEGY de conclusão (política de veredito)
│ ├── ReviewPolicy (interface)
│ └── strategies/ One/Two/ThreeReviewerPolicy
│
├── observer/ # PADRÃO OBSERVER (notificações)
│ ├── EventPublisher # publica eventos de domínio para os inscritos
│ ├── events/ DomainEvent, ArticleAssignedEvent, ArticleConcludedEvent, ReviewAttentionEvent
│ └── subscribers/ Subscriber, ReviewerAssignementEmailSubscriber,
│ AuthorResultEmailSubscriber, ReviewAttentionEmailSubscriber
│
├── email/ # PADRÃO ADAPTER (e-mail)
│ ├── EmailSender (interface)
│ ├── FakeEmailSenderAdapter # imprime no console (sem .env)
│ └── SmtpEmailSenderAdapter # envia de verdade via Jakarta Mail
│
├── records/ # DTOs (Java records) de transporte de dados
│ ├── RegisterUserData, StartEventData, SubmitArticleData, ReviewData,
│ ├── DefineReviewerAreasData, ArticleForReviewDTO, CoordinatorDashboard,
│ └── PendingDetails, EmailMessage
│
├── repository/ # CAMADA DE DADOS (Repository/DAO em memória)
│ ├── *Repository (interfaces) + InMemory*Repository (implementações)
│ └── ResettableRepository # permite "zerar" os dados por evento
│
├── enums/ # Category, Role, Verdict, ReviewStatus, ReviewOutcome
└── exceptions/ # UnauthorizedOperationException, EmailSendingException
| # | Padrão | Onde (classes / pacote) | Para quê |
|---|---|---|---|
| 1 | Facade | facade/CoordinatorFacade, facade/ResearcherFacade (interfaces CoordinatorActions, ResearcherActions) |
Uma fachada por ator, escondendo a complexidade dos vários serviços por trás de uma interface simples que a camada de apresentação consome. |
| 2 | Proxy | facade/CoordinatorAuthorizationProxy |
Mesma interface da fachada do coordenador; antes de delegar cada operação, verifica se o usuário atual é coordenador (isCurrentUserCoordinator) e lança UnauthorizedOperationException caso contrário. |
| 3 | Command | Pacote command/ (ex.: SubmitArticleCommand, InviteReviewerCommand, DefineReviewerAreasCommand) |
Encapsula cada ação do usuário como um objeto. Permite, por exemplo, adiar a definição de áreas do revisor (DefineReviewerAreasCommand) e desacopla o menu da lógica. |
| 4 | State | domain/article/ArticleState + SubmittedState, UnderReview, AcceptedState, RejectedState, AttentionState |
O Article delega as transições (startReview, accept, reject, markAttention) ao seu estado atual — cada estado sabe o que é permitido, eliminando ifs espalhados. |
| 5 | Strategy | workflow/distribution/DistributionStrategy (+ estratégias por nº de revisores) e workflow/review/ReviewPolicy (+ políticas por nº de revisores) |
Algoritmo de distribuição de artigos e política de conclusão (quando o artigo é aceito/rejeitado/atenção) variam de forma intercambiável conforme a configuração do evento. |
| 6 | Abstract Factory | workflow/factory/ReviewWorkflowFactory + One/Two/ThreeReviewerWorkflowFactory, orquestrada por workflow/ReviewWorkflowContext |
Cria a família consistente {estratégia de distribuição + política de revisão} correspondente ao nº de revisores por artigo. É o padrão adicional do RF10 (ver abaixo). |
| 7 | Observer | observer/EventPublisher, eventos em observer/events/, inscritos em observer/subscribers/ |
Publica eventos de domínio (artigo atribuído, concluído, em atenção) e notifica os inscritos (envio de e-mail) sem acoplar quem dispara ao que reage. Inscrições feitas no ApplicationBootstrap. |
| 8 | Adapter | email/EmailSender + FakeEmailSenderAdapter / SmtpEmailSenderAdapter |
Desacopla o domínio da biblioteca concreta de e-mail (Jakarta Mail). Troca-se o fake pelo SMTP real conforme o .env, sem mudar o código que envia. |
| — | Repository / DAO (arquitetural) | repository/*Repository + InMemory*Repository |
Abstrai a persistência (aqui, em memória) atrás de interfaces, permitindo trocar a implementação sem afetar os serviços. |
| — | DTO (Java record) |
Pacote records/ |
Transporte imutável de dados entre camadas (ex.: SubmitArticleData, ArticleForReviewDTO, CoordinatorDashboard). |
EventService.startEvent cria o SubmissionEvent (único/atual), descarta os dados
do evento anterior chamando deleteAll() em todos os ResettableRepository
(áreas, revisores, artigos, avaliações) e configura o ReviewWorkflowContext com a
factory correta. Acionado pelo menu do coordenador → opção 1; dados coletados em
StartEventData (nome, cidade, período, categoria e nº de revisores por artigo).
AccountService.registerResearcher cadastra o pesquisador (e-mail como chave, nome,
senha, instituição — RegisterUserData), rejeitando e-mail duplicado. Disponível no
menu inicial → opção 1.
CommitteeService.registerArea (coordenador → opção 2) cadastra as áreas e impede
duplicatas. Do lado do revisor, no 1º login a ReviewerAreaSetupScreen +
DefineReviewerAreasCommand chamam CommitteeService.defineExpertiseAreas para
registrar as áreas de expertise, usadas depois na distribuição.
CommitteeService.inviteResearcher (coordenador → opção 3) transforma um
pesquisador já cadastrado em revisor (cria um ReviewerProfile). A categoria do
evento (Full/Short/Demo, enum Category) é definida no start pelo coordenador
(chair). Para facilitar escolher o ID, a tela lista os usuários cadastrados antes
de pedir o ID.
ArticleReviewService.submitArticle (pesquisador → opção 1) registra título,
resumo, coautores e áreas (SubmitArticleData) — só aceita dentro do prazo
(event.isSubmissionOpen()), senão informa mensagem adequada. Cada artigo recebe um
ID e nasce no estado SubmittedState (State). O autor pode consultar seus
artigos via listMyArticles. A tela lista usuários e áreas para o autor obter os
IDs de coautores e temas.
DistributionService.distributeArticles (coordenador → opção 4) usa a
Strategy de distribuição ativa (ReviewWorkflowContext.getDistributionStrategy)
para dividir os artigos entre revisores por afinidade de área, de forma balanceada,
sem atribuir a um autor o próprio artigo. Cada atribuição gera um ArticleAssignedEvent
(Observer) que dispara o e-mail ao revisor com o artigo e o prazo. O
blind-review é garantido porque o ArticleForReviewDTO entregue ao revisor não
expõe os autores.
ArticleReviewService.submitReview (pesquisador/revisor → opção 2) registra o
parecer (contribuições, críticas e veredito — enum Verdict). A política de
revisão (ReviewPolicy, Strategy) decide o desfecho (aceito / rejeitado / atenção)
a partir do conjunto de pareceres, e o Article transita de estado (State). A tela
lista os artigos atribuídos ao revisor, mostrando o ID da avaliação que ele
deve informar.
EventService.getDashboard (coordenador → opção 5) monta o CoordinatorDashboard:
nº de artigos submetidos, revisores, artigos avaliados e pendentes, além da relação de
pendências (PendingDetails: artigo, revisor responsável e prazo).
Implementada por Observer + Adapter. Quando um artigo é concluído, o
ArticleConcludedEvent é publicado e o AuthorResultEmailSubscriber envia o resultado
com os pareceres; casos de empate geram ReviewAttentionEvent →
ReviewAttentionEmailSubscriber. O envio é real via SmtpEmailSenderAdapter
(Jakarta Mail) quando o .env está configurado; sem credenciais, o
FakeEmailSenderAdapter imprime o e-mail no console.
Requisito próprio da equipe: o coordenador escolhe 1, 2 ou 3 revisores por artigo
ao iniciar o evento. Essa escolha usa o Abstract Factory
(ReviewWorkflowFactory → One/Two/ThreeReviewerWorkflowFactory, selecionada em
EventService.selectFactory) para criar de forma consistente a dupla
{estratégia de distribuição + política de conclusão} adequada àquela quantidade,
mantida no ReviewWorkflowContext. Assim, distribuição e regra de veredito nunca
ficam incompatíveis entre si.
- SOLID / baixo acoplamento: dependências por interface (fachadas, repositórios,
estratégias,
EmailSender); injeção manual centralizada noApplicationBootstrap. - Encapsulamento e reuso: regras no domínio/serviços; estratégias compartilham base
comum (
BalancedDistributionStrategy). - Tratamento de exceções:
UnauthorizedOperationException,EmailSendingExceptione validações de estado/prazo. - Saída centralizada: a apresentação concentra a exibição na
ConsoleView, evitandoSystem.out.printlnespalhado pela lógica de negócio. - Execução fácil (E1/E2): cenário pré-populado pelo
DataSeedere JAR executável com um único comando.
Os diagramas de apoio estão na pasta diagrams/:
| Arquivo | Conteúdo |
|---|---|
DomainDiagram.(png/svg) |
Modelo de domínio (entidades e relações). |
LayersDiagram.(png/svg) |
Visão em camadas (apresentação / lógica / dados). |
PatternsDiagram.(png/svg) |
Onde cada padrão de projeto se encaixa. |
states.(png/svg) |
Máquina de estados do Article (padrão State). |
*.puml |
Fontes PlantUML dos diagramas acima. |
Padrões de Projeto de Software — Prof. Alex Sandro C. Rêgo — Equipe 1 (Davi, Arthur, Heitor).