Camada de Negócio

Introdução

A camada de negócio é responsável por implementar as regras de negócio da aplicação.

Ela é composta por features. Cada feature tem seu próprio pacote Java e implementa 1 (uma) funcionalidade negocial.

A funcionalidade negocial deverá ser tão próxima possível de uma Função Transacional (FT) nos termos da APF (Análise de Pontos de Função), ou seja, cada feature seria um processo elementar que constitui uma transação completa e significativa no ponto de vista do usuário. Dessa forma, a organização do código-fonte fica muito próxima do negócio, o que aumenta a legibilidade e manutenibilidade da aplicação.

O presente padrão será doravante chamado de Feature Service.

As seções seguintes detalham o padrão Feature Service, que inclui a caracterização dos três estereótipos de classe definidos para organizar o código-fonte da camada de negócio:

  • Serviço, que representa a funcionalidade em si;
  • Componente reusável , que contém lógica compartilhada entre serviços;
  • e Fragmento, que contém trecho da lógica de um serviço ou componente.

Vantagens do Padrão

O modelo de desenvolvimento da arquitetura Cloudsupport baseado em features tem as seguintes vantagens:

  • Código-fonte orientado ao requisito, oferecendo maior legibilidade e rápida identificação do escopo funcional da aplicação.

  • Encapsulamento das funcionalidades, o que permite crescimento mais estável do código-fonte, pois a inclusão de novas funcionalidades, em geral, não impactam nas classes existentes.

  • Ausência de classes centralizadas, aquelas que crescem indefinidamente com o aumento da quantidade de funcionalidades e implicam difícil manutenção, como ocorre em anti-padrões como Entity Servive.

  • Possibilidade de geração de um catálogo de funcionalidades, que seria o escopo funcional da aplicação, a partir de inspeção automatizada do código-fonte.

  • Possibilidade de cálculo automatizado de uma estimativa de tamanho funcional em Pontos de Função.

  • Permite análise eficaz de indicadores de referência cíclica e de referência transitiva entre pacotes.

  • Leitura linear do código, sem referência cruzada entre classes e entre pacotes, em aderência ao pattern Acyclic Dependencies Principle.

  • Maior clareza na automação de teste e análise de cobertura, vez que os scripts de testes estarão associados a classes de funcionalidades.

  • Melhor adequação a equipes em que vários desenvolvedores atuam concorrentemente na manutação do código.

Desvantagens:

  • Maior quantidade de classes, o que aumenta a probabilidade de colisão de nomes.

Convenções de Nomes

Cada feature deve ser nomeada conforme as seguintes diretrizes:

  • Adota-se o formato CamelCase.

  • O nome inicia em minúsculo quanto utilizado em pacote Java e em maiúsculo nas classes.

  • O nome é composto tipicamente de “Ação” (verbo no infinitivo) + “Complemento”.

  • Siglas são tratadas como palavras no CamelCase. Exemplos: “Rg”, “Uf”, “CpfDestino” e não “RG”, “UF” e “CPFDestino”.

  • Abrevie somente palavras grandes (tipicamente acima de ~10 letras).

  • Considera-se verbos comuns: Cadastrar, Alterar, Remover, Pesquisar, Detalhar, Gerar, Desfazer.

Exemplos de nomes de features:

GerarOrdemServico
CancelarOrdemServico
EncaminharOrdemServico
ExecutarOrdemServico
AtestarOrdemServico
GlosarOrdemServico
PesquisarOrdensServico

As features são organizadas conforme a estrutura de pacotes detalhada a seguir.

Estrutura de Pacotes

A camada de negócio tem a seguinte estrutura de pacotes:

Pacote Descrição
.services.web Funcionalidades na forma de webservices.
.services.jobs Funcionalidades na forma de rotinas ou processos batch.
.services.messaging Funcionalidades na forma de eventos de mensageria.
.services.common Componentes compartilhados.

Os pacotes acima contêm subpacotes para melhor organização do código.

Cada funcionalidade, por sua vez, tem seu próprio pacote.

Exemplo de estrutura de pacotes da camada de negócio de um microsserviço de gerenciamento de Ordens de Serviço (OS):

services
   web
      apoio
         pesquisarMotivosCancelamento
         pesquisarSituacoesOs
         pesquisarTiposExecutores
      executor
         cadastrarExecutor
         inativarExecutor
         pesquisarExecutores
         reativarExecutor
      ordemServico
         atestarOrdemServico
         cancelarOrdemServico
         encaminharOrdemServico
         executarOrdemServico
         gerarOrdemServico
         glosarOrdemServico
         pesquisarOrdensServico

Dentro do pacote de cada feature, as classes são implementadas conforme os estereótipos, ou “tipos” de classes, explanados a seguir.

Estereótipos de Classes

O estereótipo, no contexto da presente arquitetura, é uma marcação na classe, via anotação Java, para indicar seu objetivo. Isso é muito importante para que outros desenvolvedores possam entender melhor seu código e realizar manutenções ou correções.

Os estereótipos aqui propostos fazem parte do padrão Feature Service da arquitetura Cloudsupport e visam tornar o código-fonte mais legível. A responsabilidade da classe Java, no sentido do que ela pode ou não pode fazer, depende de seu estereótipo.

Cada feature é composta por uma ou mais classes, conforme a necessidade de organização do código-fonte. A presente arquitetura define três estereótipos, seguindo o Single Responsibility Principle, para as classes da feature:

  • @Service: Utilizado na classe que implementa a funcionalidade propriamente dita, ou seja, a classe que contém o ponto de entrada da transação. Existe sempre e apenas 1 (uma) classe de serviço por feature. Cada funcionalidade possui seu próprio pacote Java.

  • @Component: Utilizado nas classes que implementam regras de negócio reusadas em duas ou mais funcionalidades. As classes de componente ficam no pacote .common.<assunto>, e não dentro do pacote da feature.

  • @Fragment: Quando o código-fonte da classe de serviço ou do componente reusável se torna extenso, ele pode ser dividido em pedaços, sendo cada um deles uma classe @Fragment. A classe de serviço (ou componente) pode ser dividida em várias classes de fragmento, quantas forem necessárias para trazer clareza e legibilidade para o código-fonte. Os fragmentos ficam localizados no mesmo pacote da classe que o utiliza (ou em subpacotes para melhor organização).

A seguir são definidas as diretrizes para cada estereótipo.

@Service

  • Herdam de BaseService e são anotados com @Service.

  • Recebem o nome da funcionalidade mais sufixo Service. Exemplo: “CadastrarExecutorService”.

  • São singletons e stateless, em que não se deve manter estado em atributos.

  • Controlam transação, via @Transactional (do Spring) em seu método público.

    • Evitar throws na assinatura do método transacional pois, por padrão, @Transactional faz rollback somente para exceções unchecked.

    • Declarar @Transactional(readOnly = true) caso o serviço realize apenas consulta no banco de dados.

  • Quando invocados, a qualquer momento, devem sempre levar o sistema a um estado consistente.

  • Em geral representam uma Função Transacional, no sentido de APF.

  • Contêm, via de regra, apenas 1 (um) método público, referente ao ponto de entrada da funcionalidade. Exemplo: CadastrarExecutorService.cadastrar(…).

  • Podem ser divididos em fragmentos (vide classes @Fragment a seguir).

  • Usualmente são classes pequenas (até 200 linhas úteis).

  • Podem fazer LOG.

  • Podem tratar exceção, desde que haja regra de negócio definida para o fluxo de erro.

  • Devem lançar exceção do tipo BusinessException para emitir mensagens de erro de negócio, que serão exibidas para o usuário.

  • Não possuem getters e setters de atributos injetados.

  • Recebem parâmetros externos ao serviço e retornam dados através de DTOs (Data Transfer Objects) e nunca com uso de entidades persistentes. Os DTOs:

    • São colocados no pacote da feature.

    • São prefixados com o nome da funcionalidade.

    • Não precisam do sufixo DTO.

    • Possuem o sufixo Params se for DTO de entrada ou Retorno se for de saída.

    • Podem possuir a anotação de classe @JsonIgnoreProperties(ignoreUnknown = true).

Exemplo de classe de serviço:

@Service
public class CancelarOrdemServicoService extends BaseService {

    @Transactional
    public void cancelar(CancelarOrdemServicoParams params) {

        // Valida os parametros
        this.validarParametros(params);

        // Obtém a entidade e valida se a OS pode ser cancelada
        OrdemServico os = findByUid(OrdemServico.class, params.getUid());
        this.validarPodeCancelar(os);

        // Faz o cancelamento        
        os.setDataCancelamento(OffsetDateTime.now());
        os.setSituacaoOrdemServico(SituacaoOrdemServico.CANCELADA);
        os.merge(); // Active Record Cloudsupport
    }

    private void validarParametros(CancelarOrdemServicoParams params)
    {
        if (params.getUid() == null)
            throw new BusinessException("Informe a identificação da Ordem de Serviço.");
    }

    private void validarPodeCancelar(OrdemServico os)
    {
        if (SituacaoOrdemServico.CANCELADA.equals(os.getSituacaoOrdemServico()))
            throw new BusinessException("A Ordem de Serviço já está cancelada.");

        if (SituacaoOrdemServico.EM_ABERTO.equals(os.getSituacaoOrdemServico()))
            return;

        throw new BusinessException("A Ordem de Serviço está "+ os.getSituacaoOrdemServico() 
          + " e não pode ser cancelada.");
    }
}

Importante: É desencorajada a implementação de classe de serviço na qual métodos privados invocam outros métodos privados. Quando isso ocorre, é orientado que se criem os fragmentos, explanados a seguir.

@Component

  • Herdam de BaseComponent e são anotados com @Component (do Spring).

  • Têm sufixo Component.

  • São singletons e stateless, em que não se deve manter estado em atributos.

  • Não controlam ou delimitam transação.

  • Em geral representam um CIR (Componente Interno Reusável), no sentido de APF.

  • São necessariamente reusados por mais de uma feature.

  • Não são funcionalidades completas.

  • Ficam no pacote .services.common.<assunto>.

  • Contêm normalmente poucos métodos públicos.

  • Usualmente são classes pequenas (até 200 linhas úteis).

  • Podem fazer LOG.

  • Podem tratar exceção, desde que haja regra de negócio definida para o fluxo de erro.

  • Devem lançar exceção do tipo BusinessException para emitir mensagens de erro de negócio, que serão exibidas para o usuário.

  • Não possuem getters e setters de atributos injetados.

  • Podem receber parâmetros e retornar dados na forma de entidades persistentes.

@Fragment

  • Herdam de BaseFragment e são anotados com @Fragment.

  • Têm sufixo Fragment.

  • São singletons e stateless, em que não se deve manter estado em atributos.

  • Usualmente não controlam ou delimitam transação, salvo se necessário, como, por exemplo, quando há isolamento de transação em fluxos alternativos. Caso declarar escopo transacional, configure explicitamente o tipo de propagation.

  • Essencialmente servem para subdividir o código procedural de um @Service, de um @Component ou mesmo de outro @Fragment.

  • Devem estar contidos no pacote (ou subpacote) correspondente à classe que o utiliza.

  • Tem seu uso restrito à funcionalidade ou componente para o qual foi projetado.

  • Não são regras de negócio reusáveis. Para regras reusáveis, utilize @Component.

  • Contêm normalmente poucos métodos públicos.

  • Usualmente são classes pequenas (até 200 linhas úteis).

  • Podem fazer LOG.

  • Podem tratar exceção, desde que haja regra de negócio definida para o fluxo de erro.

  • Devem lançar exceção do tipo BusinessException para emitir mensagens de erro de negócio, que serão exibidas para o usuário.

  • Não possuem getters e setters de atributos injetados.

  • Podem receber parâmetros e retornar dados na forma de entidades persistentes.

Exemplo de funcionalidade em que o código do serviço foi dividido em fragmentos:

@Service
class RefaturarOrdemServicoService extends BaseService {

   @Inject RefaturarOrdemServicoValidacaoFragment validacaoFragment;
   @Inject RefaturarOrdemServicoCalculoFragment   calculoFragment;

   @Transactional
   public void gerar(...) {
      //...
      validacaoFragment.validar(...);
      calculoFragment.calcular(...);
      //... 
   }
}

@Fragment
class RefaturarOrdemServicoValidacaoFragment extends BaseFragment {
   public void validar(...) {...}
   private void validarDadosCliente(...) {...}
   private void validarLancamentosCobranca(...) {...}
}

@Fragment
class RefaturarOrdemServicoCalculoFragment extends BaseFragment {
   public void calcular(...) {...}
   private void calcularCobrancas(...) {...}
   private void calcularCreditos(...) {...}
   private void calcularImpostos(...) {...}
}

Operações de Persistência

O acesso à persistência é realizado a partir das classes de negócio, sejam serviços, fragmentos ou componentes, com a utilização:

  1. da API de consultas básicas disponibilizada por herança de BaseService, BaseFragment ou BaseComponent.

  2. da API de operações básicas de alteração da dados disponibilizada por Active Record nas entidades persistentes.

  3. da API EntityManager do JPA para operações mais elaboradas, que inclui SQL nativo, quando for necessário.

Operações de Consulta

A biblioteca Cloudsupport oferece alguns métodos para que as classes de negócio possam realizar consultas básicas de dados. Estão disponíveis por herança nas classes de serviço, fragmentos e componentes. Nos exemplos a seguir, considere Entidade uma classe de entidade persistente mapeada com JPA e en uma instância da entidade.

Método Descrição
super.findById(Entidade.class, id, fetch, lockMode) Retorna uma instância da entidade a partir do “id”.
super.findByUid(Entidade.class, uid, fetch, lockMode) Retorna uma instância da entidade a partir do “uid”.
super.findByAttr(Entidade.class, attr, value, fetch, lockMode) Retorna uma instância da entidade a partir da atributo de nome “attr” cujo valor é unicamente identificado pelo parâmetro “value”.
super.findAll(Entidade.class, fetch, orderBy, orderAsc) Retorna todas as instâncias da entidade. Parâmetro orderBy é o nome do campo a ser ordenado e orderAsc vale true para ordenação ascendente.
super.cb() Atalho que retorna o CriteriaBuilder do EntityManager.
super.setFetchJoin(from, fetch) Método útil que aplica FETCH JOIN em um “from” do Criteria JPA, em que fetch é uma String contendo os caminhos das associativas, separados por vírgula.

Em todos os casos acima, o parâmetro fetch é uma String contendo os caminhos das associativas, separados por vírgula, que terão FETCH JOIN.

Operações de Alteração de Dados

A arquitetura provê operações básicas de alteração de dados por Active Record. Nos exemplos a seguir, considere Entidade uma classe de entidade persistente mapeada com JPA e en uma instância da entidade.

Operação Descrição
en.persist() Cadastra a entidade no banco de dados. Equivale a “entityManager.persist(en); entityManager.flush();”. A entidade migra do estado transient para persistent. Utilize “en.persist(false)” para não forçar o flush imediato. Nota: Fazer persist() em instâncias detached implicará exceção.
en = en.merge() Atualiza a entidade no banco de dados. Equivale a “en = entityManager.merge(en); entityManager.flush();”. Se a entidade for detached, a instância persistente é obtida da sessão JPA (ou a partir do banco de dados) e atualizada. Se a entidade for transient, a entidade é criada no banco de dados. A instância persistente é obtida no retorno do método. Utilize “en.merge(false)” para não forçar o flush imediato.
en.remove() Remove a entidade do banco de dados. Equivale a “entityManager.remove(en); entityManager.flush();”. Se a entidade for detached ou transient, ocorrerá exceção. Utilize “en.remove(false)” para não forçar o flush imediato.

Comentário: As operações do Active Record do Cloudsupport fazem flush por padrão. São motivações para esse comportamento padrão:

  • Código mais legível, pois incentiva o desenvolvedor a seguir a ordem natural das operações de banco de dados. Exemplo:

    • Suponha uma entidade Pessoa que tenha uma associação de “muitos para um” com Endereco (pessoa.endereco). Será necessário primeiro persistir o Endereco (para que este receba o ID) e depois o objeto Pessoa. Sem o flush, o JPA permitiria a ordem inversa dessas operações (persistir Pessoa antes de Endereco), mas, neste caso, o JPA primeiro faria o insert de Pessoa com o campo “endereco_id” nulo e depois executaria uma instrução SQL extra para vincular a chave estrangeira chave entre Pessoa e Endereco (update pessoa set endereco_id = :id). Observe que há um custo computacional extra nesse caso.
  • Garantia de que qualquer instrução SQL nativa será executada com o banco de dados atualizado, o que mitiga erros de lógica (como selecionar pessoas com endereco nulo).

  • Pouco ou nenhum impacto de performance.

    • Quando existe mapeamento de campo auto gerado na entidade (que é o mais comum, devido ao @GeneratedValue no ID), a inserção/atualização em massa do Hibernate é desativada. Portanto, não forçar o flush provavelmente não proporcionaria ganho de desempenho. Para operações em massa (bulk update/delete), prefira usar CriteriaUpdate, CriteriaDelete ou SQL nativo.

Operações via EntityManager

As classes de negócio têm acesso completo ao EntityManager para operações de persistência, podendo ser utilizados quaisquer recursos do JPA, conforme necessidade. No que se refere a consultas, o JPQL (Java Persistence Query Language) traz simplicidade e produtividade, podendo ser utilizado livremente.

Método Descrição
super.em() Retorna o EntityManager.

SQL Nativo

SQL nativo é recomendado nos seguintes cenários:

  1. Operações de alta performance que exigem hints específicos do SGBD.

  2. Relatórios e consultas desnormalizadas.

  3. Statements específicos do SGBD não existentes na API do JPA.

Utilize SQL nativo via EntityManager.

Tipos de Funcionalidades

A presente arquitetura define padrões para dois tipos relevantes de features, apresentados abaixo. Outros tipos de funcionalidades podem ser implementados conforme a necessidade do projeto.

Webservices

São funcionalidades projetadas para consumo pelos frontends, sejam aplicativos móveis ou páginas web, bem como para integrações com outros microsserviços. As aplicações de backend são tipicamente compostas de webservices.

Dedicamos seção específica para esse tema: Webservices.

Rotinas (Jobs)

São funcionalidades projetadas para execução em background. São úteis para processos em lote (batch) ou processos de fluxo contínuo (fila ininterrupta, como, por exemplo, envio de notificações).

Dedicamos seção específica para esse tema: Rotinas.

Logging

As classes de negócio possuem o atributo logger, por herança, relativo ao framework de logging. Esse atributo é declarado da seguinte maneira nas classes mãe:

protected final Logger logger = LoggerFactory.getLogger(getClass());

As classes Logger e LoggerFactory são do pacote org.slf4j. O framework que implementa a API do SLF4J é o Log4j2.

As configurações de logging para o ambiente de desenvolvimento local encontram-se no arquivo logging.xml. Os projetos arquétipos incluem uma configuração padrão. Nos ambientes de teste/homologação e produção, as configurações de logging são injetadas na aplicação pelo Kubernetes.

Rastreamento de Falhas

A biblioteca Cloudsupport possui um módulo para rastreamento de falhas, que faz tratamento automático e padronizado de exceções não tratadas pelo desenvolvedor.

Quando ocorre uma exceção não tratada, seja em um webservice ou rotina, é gerado um código único de rastreamento, e somente essa informação é retornada para o usuário externo (por exemplo, o requisitante do webservice). Um log nível error é emitido no seguinte formato:

Unexpected exception, tracking: CODE, error: [SIMPLE_CLASS_NAME: MESSAGE], root cause: [SIMPLE_CLASS_NAME_ROOT: MESSAGE_ROOT]

Note que a linha de log contém o código de rastreamento, chamado de tracking, e um resumo com base na exceção final e na causa raiz, o que facilita uma rápida análise do erro.

Trata-se de uma boa prática para:

  • Uniformizar o padrão de mensagens de erro

  • Facilitar a localização das falhas com base em código único de rastreio

  • Evitar que dados sensíveis sejam repassados para os clientes da aplicação

O stack trace completo é registrado junto com o log.

O capítulo Webservices descreve como o tracking é retornado via HTTP e o capítulo Rotinas cita os campos no banco de dados no qual o Cloudsupport persiste o resumo do erro.

Gerando Rastreamento

O desenvolvedor, ao registrar manualmente um log de erro, pode manter o padrão da arquitetura conforme o snippet de código abaixo:

@Inject
private TrackingGenerator trackingGenerator;

// ...

String tracking = trackingGenerator.generate();
String errorMessage = ExceptionUtils.messageFrom(ex, tracking);

logger.error(errorMessage, ex);

A string tracking será um novo código gerado pelo TrackingGenerator. A string errorMessage segue o padrão da arquitetura, conforme seção acima, que contém o código de rastreamento e um resumo com base na exceção final e na causa raiz.

Opcionalmente, para contextuar o log, pode-se prefixar errorMessage com dados específicos, por exemplo:

logger.error(String.format("Erro ao processar item '%s'. %s", itemId, errorMessage), ex);

Personalizando o Código

Implemente na aplicação um Bean da interface TrackingGenerator para personalizar o formato dos códigos de rastreamento.

Por padrão, o Cloudsupport autoconfigura um gerador de códigos de rastreamento com base na propriedade cloudsupport.error.tracking-generator:

  • "date" (padrão): O código terá a sintaxe "<YYMMDD>-<serviceId>-<random>", onde <serviceId> é os primeiros 4 dígitos do hash de spring.application.name e <nandom> é o hexa do UID de 54-bit da biblioteca Cloudsupport. Substituições são feitas para melhor legibilidade e fonética (b -> h, c -> k, d -> x, e -> w).

    Exemplo: "251025-4a6w-247612h6xk280".

  • "uuid": o código será a versão Base64 do UUID Java (ex: "acRFQHdRSiuNW_g9vBoh3Q")

Ambos os geradores salvam o código no contexto MDC do Log4j2 errorTracking.

UID

O uid deve ser utilizado como chave das entidades que atravessam a fronteira da aplicação. Não exponha o id sequencial de banco.

Diretrizes:

  • O uid deve ser declarado na entidade preferencialmente com o tipo Long. Strings podem ser utilizadas, porém reduzem a performance do banco.

  • O uid deve ser preenchido pelo microsserviço quando a entidade é cadastrada no banco de dados. A biblioteca Cloudsupport disponibiliza a classe utilitária UidGenerator para essa finalidade:

    //...
    entidade.setUid(UidGenerator.nextLong());
    entidade.persist(); 
    
  • No caso de utilização de outro gerador de UID tipo Long, atente-se:

    • À compatibilidade com o tipo numérico de JSON. No JavaScript os números têm 53 bits de precisão, enquanto o Java são 64 bits. Dessa forma, o gerador de UIDs deve trabalhar dentro da zona numérica segura, para evitar truncamento durante a serialização do DTO para JSON e consequentemente causar um bug silencioso, de difícil identificação.

    • À performance.

    • Ao risco de colisão, especialmente quando houver alta concorrência.

Anti-Padrões

Ressalta-se que os padrões DAO, Repository, Rich Domain e Entity Service não devem ser utilizados.

  • O padrão Rich Domain orienta que as operações de negócio sejam implementadas em classes de entidade. Essa abordagem é totalmente desencorajada, pois tende a resultar em classes muito grandes e com alta taxa de referência cíclica. Ela também fere ao princípio da única responsabilidade (SRP, Single-Responsibility Principle), pois uma mesma classe conterá a implementação de muitas funcionalidades do negócio. Note ainda que isso aumenta o risco de conflitos na reintegração de código.

  • O padrão Entity Service orienta que as operações de negócio sejam implementadas em classes que representam entidades fortes. Essa abordagem é desencorajada, pois incorre nos mesmos problemas do Rich Domain.

  • O padrão REST orienta que as operações de negócio sejam organizadas na forma de CRUDs sobre entidades fortes (ou resources). Nesse requisito, o padrão REST não é recomendado, vez que produz uma API menos legível e que requer um esforço maior de documentação bem como de padronização para garantir uma consistência lógica conforme o crescimento funcional da aplicação. A presente arquitetura recomenda que a API seja orientada ao requisito, com a utilização de verbos, para que a API resultante seja clara e intuitiva, algo como é feito em APIs RPC. Entretanto, são bem-vindas do REST as orientações de utilização do formato JSON bem como de códigos de retorno HTTP. Sugere-se evitar SOAP e XML.

  • O padrão DAO (Data Access Object) define uma camada que abstrai as operações de persistência. O EntityManager do JPA atua, dentre outras funções, como DAO, de forma que não se faz necessário implementar manualmente essa camada.

  • O padrão Repository é semelhante ao DAO, porém atua em um nível mais alto de abstração, como na agregação e organização de objetos de domínio em memória. A implementação de Repository oferecida pelo framework Spring Data não permite encapsulamento das operações dentro de features. Nele, a classe de repositório é única por entidade e cresce indefinidamente com a evolução da aplicação, o que implica sérios problemas de manutenibilidade. Por esse motivo o Repository do Spring Data não deve ser utilizado.

Próximos Passos

A próxima leitura sugerida é a seção Webservices, que detalha os endpoints de webservice da camada de negócio.