Pular para conteúdo

Engenharia - Requisição de Feedback e Resposta de Feedback

Visão Geral

Nessa sprint iremos desenvolver o módulo em que em o colaborador cria seu questionário de feedback com três ou mais perguntas e seleciona um ou mais avaliadores externos (colaboradores dos clientes CI&T para o quais estão alocadpos). Uma vez criado o questionário o mesmo vai para aprovação do PDM, caso aprovado os avaliadores recebem um email automáticamente solicitando que respondam a avaliação acompanhao de um link para a mesma. Uma vez respondido o questionário, o PDM pode visualizar as respostas com os dados de quem a respondeu assim como o colaborador também pode, contudo, o colaborador não pode ver os dados de quem respondeu, configurando assim uma visualização anônima para o colaborador. É importante ressaltar que o questionário tem uma validade de 3 meses a partir de sua aprovação pelo PDM

mmd

1. Entendimento do Domínio

Esse módulo requer a criação de um domínio um pouco mais robusto que o anterior, além disso possui uma máquina de estados a ser gerencia para que o status da requisição seja acompanhado pelos usuários envolvidos. Muito importante lembrar que campos de status são raramente necessários quando temos outros dados na entidade que podem ser aferidos para constatar o estado atual da requisição, dessa forma não quebramos a terceira forma normal (3NF - Não persisitir campos que podem ser calculados através de outros campos) que visa remover campos redundantes que podem levar a estados inconsistentes:

«Entity»FeedbackRequestid: UUIDrequester_id: LongcreatedAt: DateTimeapprovedAt: DateTimerejectedAt: DateTimeeditedAt: DateTimeappraisers: List<Appraiser>isApproved(): booleanisRejected(): booleanisEdited(): booleanisExpired(): boolean«ValueObject»Appraisername: stringemail: stringrespondedAt: DateTimeisResponded(): boolean

Essa abordagem evita um estado inconsistente ao não utilizar campos de status com enumeradores ou booleanos, e, em vez disso, define métodos que encapsulam a lógica de estado dentro da própria classe. Vamos explorar essa abordagem com exemplos e explicações.

Exemplo

class FeedbackRequest {
    public boolean isApproved() {
        return approvedAt != null && (rejectedAt == null || approvedAt.isAfter(rejectedAt));
    }

    public boolean isRejected() {
        return rejectedAt != null && (approvedAt == null || rejectedAt.isAfter(approvedAt));
    }

    public boolean isEdited() {
        return editedAt != null;
    }

    public boolean isExpired() {
        return createdAt.plusMonths(3).isBeforeNow();
    }
}

class Avaliator {
    public boolean isResponded() {
        return respondedAt != null;
    }
}

Observações

  1. Evita Ambiguidade de Estado:

    • Em vez de usar um campo de status (como um enumerador STATUS com valores PENDING, APPROVED, REJECTED), a lógica de aprovação e rejeição é encapsulada em métodos. Isso impede estados inconsistentes, como um feedback que poderia ser simultaneamente "aprovado" e "rejeitado" se os campos não forem geridos corretamente.
    • Por exemplo, no método isApproved(), verificamos se approvedAt não é nulo e garantimos que, se rejectedAt também estiver definido, a aprovação ocorreu antes da rejeição. Isso garante que apenas um estado possa ser verdadeiro por vez.
  2. Facilidade de Manutenção:

    • A lógica de negócios está centralizada dentro da entidade, facilitando a manutenção. Se houver necessidade de alterar a lógica de validação, isso pode ser feito em um único lugar, ao invés de modificar o tratamento de status em várias partes do código.
  3. Complexidade nas Queries HQL:

    • Uma desvantagem dessa abordagem é que, ao não utilizar campos de status, as consultas HQL se tornam mais complexas. Para filtrar objetos com base em estados calculados, você deve usar os campos armazenados no banco de dados.
    • Por exemplo, ao consultar feedbacks aprovados, a lógica deve ser incorporada na consulta HQL:
    List<FeedbackRequest> approvedRequests = session.createQuery(
        "FROM FeedbackRequest WHERE approvedAt IS NOT NULL AND (rejectedAt IS NULL OR approvedAt > rejectedAt)", 
        FeedbackRequest.class).list();
    

Avaliator é um Value Object?

O Avaliator é um exemplo de Value Object, que encapsula um conjunto de atributos que definem um avaliador. Ele não possui identidade própria (ou seja, dois avaliadores com o mesmo nome e email são considerados iguais) e seu valor é baseado unicamente em suas propriedades. Contudo é importante observar que ele pode também ser uma entidade agregada por questões de performance, contudo, possuirá um id apenas para compor como chave estrangeira, uma vez que o mesmo avaliador pode aparecer diversas vezes em diferentes requisições de feedback. Esse tipo de abordagem apesar de não ser ótima em relação ao DDD traz vantagens na indexação, busca e agrupamento dentro de um banco de dados relacional.

Decisão entre Value Object e Entidade Agregada

Uma entidade agregada é uma entidade cuja é apenas uma dependência de outra, ou seja, a mesma não precisa de acesso direto via repositorio, serviço ou controller, seu dado sempre será manipulado apenas através dos endpoints de sua entidade agregadora

Imagine o seguinte put para o endpoint de requisições de feedback, esse dado deveria atualizar a requisição e a pergunta em questão.

{
    id: 1,
    ...
    perguntas: [
        {
            id: 1
            text: "avalie meu desempenho"
        }
    ]
}

Nota: Essa abordagem pode ser ruim em um contexto em que a coleção associada for muito extensa, porém nesse cenário aonde teremos poucas perguntas pode ser manipulado dessa forma sem grandes complicações.

JPA Cascade

Lembre-se que para ter o resultado esperado nesse tipo de situação é necessário usar o cascade adequado no mapemento JPA como:

@OneToMany(mappedBy = "request", cascade = CascadeType.ALL)
private List<Pergunta> perguntas

Mais detalhes aqui

Integração do usuário com o Feedback Request

A classe LoggedUser faz parte da biblioteca de segurança e é utilizada para representar um usuário autenticado no sistema. Quando uma requisição recebe um token JWT (JSON Web Token), um filtro de segurança extrai os dados desse token e preenche uma instância da classe LoggedUser.

Esta documentação se concentra em como usar a classe LoggedUser para popular entidades que dependem das informações do usuário, especialmente o ID do usuário, evitando a necessidade de mapeamento de uma entidade de usuários completa.

Estrutura da Classe LoggedUser

Atributos

  • Long id: Identificador único do usuário.
  • String name: Nome do usuário.
  • String email: Endereço de e-mail do usuário.
  • String type: Tipo de usuário (por exemplo, papel ou perfil de acesso).
  • Long PDM: Um identificador adicional associado ao usuário.

Após a instância de LoggedUser ser preenchida com os dados do token JWT, isso é feito pela lib de security automáticamente, você pode usá-la para popular entidades que necessitam das informações do usuário autenticado. Abaixo estão exemplos de como utilizar LoggedUser para popular essas entidades.

1. Exemplo de Entidade que Depende do ID do Usuário

Suponha que você tenha uma entidade chamada FeedbackRequest que armazena informações sobre requisições de feedback feitas por usuários.

@Entity
public class FeedbackRequest {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long userId; // ID do colaborador que faz a requisição
    private String message;

    // Getters e Setters
}

2. Criação de Requisição de Feedback Usando LoggedUser

Você pode usar a classe LoggedUser para popular a entidade FeedbackRequest ao criar uma nova requisição.

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

@Service
public class FeedbackService {

    public FeedbackRequest createFeedbackRequest(String message) {
        // Recupera a instância de LoggedUser do contexto de segurança
        LoggedUser loggedUser = (LoggedUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        // Cria uma nova requisição de feedback
        FeedbackRequest feedbackRequest = new FeedbackRequest();
        feedbackRequest.setUserId(loggedUser.getId()); // Usa o ID do usuário autenticado
        feedbackRequest.setMessage(message);

        // Salvar feedbackRequest no repositório (não mostrado aqui)
        // feedbackRequestRepository.save(feedbackRequest);

        return feedbackRequest;
    }
}

3. Vantagens de Usar LoggedUser

  • Simplicidade: Facilita o acesso ao ID do usuário autenticado sem a necessidade de mapear uma entidade de usuários completa.
  • Desacoplamento: Mantém a lógica de segurança separada da lógica de negócios, melhorando a organização do código.
  • Eficiência: Reduz a necessidade de chamadas ao banco de dados para obter informações do usuário, utilizando os dados já disponíveis no token JWT.

A classe LoggedUser é uma ferramenta eficaz para gerenciar informações do usuário autenticado em aplicações que utilizam JWT. Ao utilizar esta classe, você pode facilmente popular entidades que dependem do ID do usuário, simplificando o código e melhorando a eficiência do sistema. Isso evita a necessidade de mapear entidades de usuários desnecessárias e mantém seu código limpo e fácil de manter. Mais detalhes sobre integração entre microserviços aqui

Envio de email

No momento em que um PDM aprova uma solicitação, um email para os avaliadores deve ser enviado, esse email deve conter o link para resposta do formulário que será a tela de "login" do avaliador que será confeccionada na sprint 3, será necessário planejar qual será a rota para essa tela afim de enviá-la no email

Guia de referências

Aqui segue as documentações mais importantes dessa documentação a serem lidas para essa sprint

  1. Relacionamento Entre Microservices
  2. Envio de Emails com Spring

Referências

  1. 3NF (Third Normal Form)

  2. JPA Cascade

  3. Aggregate Entity

  4. Value Object

  5. CORS (Cross-Origin Resource Sharing)

  6. Spring Email

Prompt para IA

Por favor, gere um exemplo de código em Java utilizando JPA que demonstre a implementação dos seguintes conceitos:

1. **3NF**: Estruture as entidades de forma que estejam normalizadas até a terceira forma normal.
2. **JPA Cascade**: Utilize os tipos de cascade para gerenciar a persistência de entidades relacionadas.
3. **Aggregate Entity**: Mostre como definir uma entidade agregada que encapsule regras de negócio.
4. **Value Object**: Crie um exemplo de um Value Object que represente um conceito imutável.
5. **CORS**: Configure um endpoint REST para permitir CORS.
6. **DB Weak External Key**: Inclua uma entidade fraca que dependa de uma entidade forte.

Certifique-se de que o código esteja bem comentado e siga as melhores práticas de desenvolvimento.