Como implementar isso no Back-End?

E aí, pessoal, tudo bem? Bem, vou ser sincero em dizer que estou desenvolvendo uma nova implementação que jamais fiz antes e, por isso, estou pesquisando bastante e até perguntando em fóruns para os mais experientes. Por exemplo, um projeto simples, onde um cliente possui muitos endereços, mas um endereço pertence a apenas um único cliente.

Vamos aos exemplos: No banco de dados, há dois clientes:

{
  Id: 1,
  Username: "Clark Kent",
  Password: "Admin799"
}

{
  Id: 2,
  Username: "Maite Perroni",
  Password: "Common999"
}

O primeiro cliente, "Clark Kent", será o administrador do sistema, ou seja, poderá manipular todo o sistema, permitindo adicionar novos clientes, listar todos e apagar. O segundo cliente, "Maite Perroni", será uma conta comum, ou seja, poderá manipular apenas os seus próprios relacionamentos, entre eles, adicionar um novo endereço, listar todos, atualizar e excluir.

Bem, conforme os exemplos, podemos dizer que os clientes já foram criados e fizeram login em suas respectivas contas, certo? Agora, como eu poderia implementar o caso em que o cliente "Clark Kent" deseja adicionar um novo endereço à sua conta, mas o endereço no banco de dados ficou nulo e não referenciou a conta do cliente que foi adicionado, por exemplo:

Id | Username | Password
1  | ClarkKent | Admin799

Id | Email     | Número | Client_Id
1 | ClarkKent@ | 2932183129 | null

Estou muito confuso com isso, pois minha única lógica e saída realmente é desenvolver um serviço: ClienteAddressService, que terá cada método CRUD. Vamos dar o exemplo do save:

public void createClienteAddress(int clientId, Address address) {
    // Primeiro, vai buscar no repositório para verificar se o clientId existe.
    // Caso exista, vai vincular o address à conta do cliente.
    // Também vai vincular o id do cliente na tabela de Address.
}

Assim, como funcionaria uma criação de ClienteAddressController, por exemplo:

@Route("/clients/addresses")
@Request("/clientId/addressId")
public void createClienteAddress(int clientId, Address address) {}

Sem dizer que após isso, irei implementar segurança e autorização. Está correta a minha lógica? Aceitaria muito ajuda.

Tanto o service e o controller ficou assim, conforme a lógica que tive:

SERVICE:
@Service
@AllArgsConstructor
public class UserAddressService {
    private final UserService userService;
    private final AddressService addressService;

    public void save(UUID userId, Address address) {
        var existUser = userService.getById(userId);

        existUser.getAddresses().add(address);
        address.setUserId(existUser.getId());
        addressService.save(address);
    }

    public Optional<Address> getById(UUID userId, UUID addressId) {
        var existUser = userService.getById(userId);
        var existAddress = addressService.getById(addressId);

        return existUser.getAddresses().stream()
                .filter(id -> id.getId() == existAddress.getId())
                .findFirst();
    }
}

CONTROLLER:

@RestController
@RequestMapping("/users/addresses")
@AllArgsConstructor
public class UserAddressController {
    private final UserAddressService service;

    @PostMapping("/{userId}")
    public ResponseEntity<Address> save(@PathVariable UUID userId, @RequestBody Address address) {
        var savedAddress = service.save(userId, address);

        return ResponseEntity
                .status(HttpStatus.CREATED)
                .body(savedAddress);
    }

    @GetMapping("/{userId/{addressId}")
    public ResponseEntity<Address> getById(@PathVariable UUID userId, UUID addressId) {
        var existAddress = service.getById(userId, addressId);

        return ResponseEntity
                .status(HttpStatus.OK)
                .body(existAddress.get());
    }
}

Se a relação do Usuário com o Endereço é 1:N (um usuário pode ter múltiplos endereços), então a sua tabela de endereços deve conter um constraint de uma chave estrangeira (pesquise como fazer isso com base no banco que estiver utilizando), assim o banco irá garantir que a coluna contendo o id do usuário deve corresponder a uma entrada existente na tabela de usuários.

Outra coisa que notei, é que no seu exemplo a senha está em plain text (se for só um exemplo mesmo e você já souber disso, pode desconsiderar, mas vale a pena falar sobre). Para salvar senhas dos usuários em bancos, é extremamente recomendado que se utilize hash, procurar saber mais sobre isso que é bem importante.

A senha é apenas um demo. Bem, o meu objetivo é fazer que o usuário logado ao cadastrar um novo endereço que fique salvo no id que pertence a ele, não que fica null no banco de dados. Talvez estou me expressando errado, mas fiz outro teste e sinto que to errando. ``` @RestController @RequestMapping("/users") @AllArgsConstructor public class UserController { private final UserService service; @PostMapping("/") public ResponseEntity createUser(@RequestBody User entity) { return ResponseEntity .status(HttpStatus.CREATED) .body(service.createUser(entity)); } @PostMapping("/{userId}/address") public ResponseEntity
createAddress(@PathVariable UUID userId, @RequestBody Address entity) { return ResponseEntity .status(HttpStatus.CREATED) .body(service.createAddress(userId, entity)); } @GetMapping("/{userId}") public ResponseEntity getUserById(@PathVariable UUID userId) { return ResponseEntity .status(HttpStatus.OK) .body(service.getUserById(userId)); } @GetMapping("/{userId}/{addressId}") public ResponseEntity> getAddressById(@PathVariable UUID userId, @PathVariable UUID addressId) { return ResponseEntity .status(HttpStatus.OK) .body(service.getAddressById(userId, addressId)); } } ```

Duas dicas em questão de código:

  • Tente manter a conformidade entre a nomenclatura das classes/métodos.
    • Há a mistura do português com o inglês, como é o caso de createClienteAddress.
    • Em determinado momento, o cliente passa a ser chamado de User, defina qual será a nomenclatura padrão.
  • Algumas classes podem ser substituidas por record o que lhe torna possível tirar o @AllArgsConstructor e consequentemente a dependência do Lombok.

Sobre a resolução do caso em si: Parece que a falha está no DDL do Banco de Dados. Só faz sentido existir um endereço se ele está vinculado a um cliente, logo: 1. client_id deverá ser NOT NULL e/ou 2. a Primary Key deve ser uma chave composta com o próprio address_id e o client_id (isso significa que não pode existir um endereço sem um cliente).Tendo resolvido isso, nem será possível adicionar novos registros se não estiverem em conformidade com essas regras e estourará Exception se tentar.

Se este registro tiver sido feito anteriormente e só quer correção, sugiro remover o registro e adicionar novamente OU fazer a inserção manual da edição direto no SQL.

Última dica: Parece que desenhou pouco como vai ser a solução, do que precisa ou não. Não parece nem ter um desenho claro do Banco de Dados, recomendo parar de colocar a mão no código um pouco e desenhar o DER e pelo menos um modelo de UML de como será o fluxo do processo.

Isso foi apenas um teste mesmo, pois está totalmente inglês o código-fonte. Apenas estou fazendo um projeto demo para demonstrar o meu objetivo, por isso que inclui lombok para uma produtividade melhor (pois não gosto de bibiliotecas de terceiros no código). Arquitetura:https://prnt.sc/6oSSS-hKEv_x Diagrama: https://prnt.sc/mUT8cxgHmvdq Agora pourco fiz outro teste e sinto que está errado: ``` @RestController @RequestMapping("/users") @AllArgsConstructor public class UserController { private final UserService service; @PostMapping("/") public ResponseEntity createUser(@RequestBody User entity) { return ResponseEntity .status(HttpStatus.CREATED) .body(service.createUser(entity)); } @PostMapping("/{userId}/address") public ResponseEntity
createAddress(@PathVariable UUID userId, @RequestBody Address entity) { return ResponseEntity .status(HttpStatus.CREATED) .body(service.createAddress(userId, entity)); } @GetMapping("/{userId}") public ResponseEntity getUserById(@PathVariable UUID userId) { return ResponseEntity .status(HttpStatus.OK) .body(service.getUserById(userId)); } @GetMapping("/{userId}/{addressId}") public ResponseEntity> getAddressById(@PathVariable UUID userId, @PathVariable UUID addressId) { return ResponseEntity .status(HttpStatus.OK) .body(service.getAddressById(userId, addressId)); } } ```
Bom, aparentemente a abordagem parece estar correta. Só falta um refinamento no DER, como por exemplo as regras aplicadas na camada física, PKs e FKs. Além de uma coisa que não ficou claro, o User (administrator) vai criar o perfil dos clientes (customers), entretanto, como os próprios customers irão se logar? Não parece ser previsto nesse modelo que você desenhou.
Bem, agora melhorei os controllers separando pelas responsabilidades: ``` package com.example.demo.core.presentation.resources; import com.example.demo.core.domain.Address; import com.example.demo.infrastructure.gateway.services.UserAddressService; import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.UUID; @RestController @RequestMapping("/users") @AllArgsConstructor public class UserAddressController { private final UserAddressService service; @PostMapping("/{userId}/") public ResponseEntity
createAddressInUser(@PathVariable UUID userId, @RequestBody Address entity) { var createdAddress = service.createAddressInUser(userId, entity); return ResponseEntity .status(HttpStatus.CREATED) .body(createdAddress); } @GetMapping("/{userId}/{addressId}") public ResponseEntity
getAddressInUserById(@PathVariable UUID userId, @PathVariable UUID addressId) { return ResponseEntity .status(HttpStatus.OK) .body(service.getAddressInUserById(userId, addressId)); } } ``` Então está correta abordagem? Procurei em diversos artigos, stackoverflow até tutoriais de youtube e não tem nenhum conteúdo como ensina a vinculação assim entre front-end e o back atraves de endpoints após usuário já criado no banco de dados. Sim, pelo visto vou ter recriar, pois entendi que os customer na verdade são os users, a diferença é que eles vão ter roles diferentes.
Eu já vi conteúdo que explica isso, só não lembro aonde. O Telegram tem muito "conhecimento sem certificado". Mas não entendi a sua dúvida, pois a conexão do frontend com o backend através de endpoints é isso o que está fazendo, sendo isto que estamos discutindo, especificamente, o backend. Depois de criado com POST, é só consultar com GET, editar com PUT ou remover com DELETE, não tem muito segredo, aí o front consome estes métodos do backend dependendo das suas restrições de negócio.
Entendi, então outra dúvida: Fazendo esse tipo de implementação, o controller do address solo é algo que não precisa criar? pois seria algo inutil. Apenas estava com bastante duvida, pois perguntei em diversos forums, principalmente no reedit: learn java e falaram que estava incorreta essa lógica.