🎮 A Camada de Serviço (`@Service`)

🎮 A Camada de Serviço (@Service)

No SDK Sankhya, a anotação @Service designa uma classe como um ponto de entrada (endpoint) para requisições externas. Ela atua como um "Controller" no padrão MVC, orquestrando a interação entre o mundo exterior e a lógica de negócio da sua aplicação.

🤔 Por que usar @Service?

  • Clareza Arquitetural: Separa a camada de apresentação (recebimento de dados) da lógica de negócio.
  • Padrão de Mercado: Alinhado com frameworks como Spring, o que acelera a curva de aprendizado.
  • Integração Total: Funciona perfeitamente com controle transacional.
  • Separação de Responsabilidades: Mantém a classe de serviço focada em receber e delegar requisições, e não em executar regras de negócio complexas.

📊 Abordagem Antiga vs. Nova

❌ Abordagem Tradicional (SessionBean)

A abordagem antiga exigia herança de SessionBean e manipulação manual de ServiceContext, resultando em um código verboso, acoplado e difícil de testar.

// Código legado: verboso, acoplado e difícil de testar
/**
 * @ejb.bean name="PedidoServiceSP"
 * jndi-name="mge/placemm/ejb/session/PedidoServiceSP"
 * type="Stateless" transaction-type="Container" view-type="remote"
 * @ejb.transaction type="Supports"
 * @ejb.util generate="false"
 */
public class PedidoServiceSPBean extends SessionBean {
    public void criarPedido(ServiceContext sctx) throws Exception {
        JapeSession.SessionHandle hnd = null;
        try {
            hnd = JapeSession.open();
            // Lógica manual de transação, parsing de JSON, etc.
            JsonObject json = (JsonObject) sctx.getJsonRequestBody();
            // ... muito código boilerplate ...
        } finally {
            JapeSession.close(hnd);
        }
    }
}

✅ Nova Abordagem com @Service

A nova abordagem é declarativa, limpa e integrada com os demais pilares do SDK.

// Código moderno: limpo, desacoplado e fácil de testar
@Service(serviceName = "PedidoServiceSP") // Define o nome do endpoint não pode ser  o nome da classe
public class PedidoController {

    @Transactional // O SDK gerencia a transação
    public void criarPedido(PedidoDTO pedido) {
        // Lógica de negócio para criar o pedido
        PedidoBusinessService businessService = new PedidoBusinessService();
        businessService.criar(pedido);
    }
}

📝 Conceito e Regras

A anotação @Service transforma uma classe Java simples em um endpoint.

Regras e Convenções:

  1. serviceName(Atributo Obrigatório): Define o nome do serviço que será registrado na plataforma. Por convenção, deve terminar com o sufixo SP (ex: PedidoServiceSP).
  2. Métodos Públicos: Todos os métodos públicos da classe se tornam "ações" do serviço, acessíveis externamente.

URL de Acesso:
A URL para chamar uma ação é composta da seguinte forma:
.../service.sbr?serviceName=<nomeDoServico>.<nomeDoMetodo>

Exemplo:
.../mge/service.sbr?serviceName=PedidoServiceSP.criarPedido


🔗 Controle Transacional com transactionType

A anotação @Service permite configurar o comportamento transacional padrão para todos os métodos da classe através do atributo transactionType.

import br.com.sankhya.studio.core.data.TransactionType;

@Service(
    serviceName = "RelatorioServiceSP",
    transactionType = TransactionType.NotSupported // Define o padrão para a classe
)
public class RelatorioService {
    // ...
}
TransactionTypeDescriçãoQuando Usar
Required(Padrão) Se uma transação já existir, usa a mesma. Se não, cria uma nova.Ideal para operações de escrita (salvar, atualizar, deletar).
RequiresNewSempre cria uma nova transação, suspendendo a atual se existir.Casos específicos, como logs de auditoria que precisam ser salvos independentemente da transação principal.
NotSupportedExecuta o método fora de qualquer transação. Se uma transação estiver ativa, ela é suspensa.Perfeito para operações de apenas leitura (consultas), pois melhora o desempenho ao não criar sobrecarga transacional.
MandatoryExige que uma transação já exista. Se não existir, lança uma exceção.Raro, usado para garantir que um método só seja chamado dentro de um contexto transacional já estabelecido.

Sobrepondo o Padrão com @Transactional

Você pode sobrepor o comportamento definido no @Service usando a anotação @Transactional diretamente em um método.

@Service(serviceName = "ConsultaServiceSP", transactionType = TransactionType.NotSupported)
public class ConsultaService {

    // Este método usa o padrão da classe (NotSupported)
    public List<ProdutoDTO> listarProdutos() {
        // ... lógica de consulta ...
    }

    @Transactional(type = TransactionType.RequiresNew) // Sobrepõe o padrão
    public void registrarLogDeConsulta() {
        // Este método executará em sua própria transação
    }
}

✨ Boas Práticas

  • Serviços para Leitura: Para serviços que apenas realizam consultas, configure transactionType = TransactionType.NotSupported para otimizar o desempenho.
  • Separe Lógica de Negócio: Mantenha seus @Services enxutos. A responsabilidade deles é orquestrar a chamada. A lógica de negócio complexa deve ser delegada para outras classes.
  • Use DTOs: Sempre use Data Transfer Objects (DTOs) como parâmetros dos seus métodos de serviço para desacoplar sua API da sua camada de persistência.

📡 Exemplos de Requisição e Resposta

📋 DTOs de Exemplo

Antes de mostrar as requisições HTTP, vamos definir os DTOs que serão utilizados nos exemplos:

DTOs de Request

// DTO para criar pedido
public class PedidoDTO {
    private ClienteDTO cliente;
    private List<ItemPedidoDTO> itens;
    private String observacao;
    
    // getters e setters...
}
public class ClienteDTO {
    private Integer codigo;
    private String nome;
    
    // getters e setters...
}
public class ItemPedidoDTO {
    private Integer produtoId;
    private Integer quantidade;
    private Double valorUnitario;
    
    // getters e setters...
}
// DTO para filtros de consulta
public class FiltroDTO {
    private String categoria;
    private Boolean ativo;
    private Integer limite;
    
    // getters e setters...
}
// DTO para processamento de lote
public class LoteDTO {
    private String identificador;
    private List<RegistroDTO> registros;
    private ConfiguracaoDTO configuracao;
    
    // getters e setters...
}
public class RegistroDTO {
    private String tipo;
    private Map<String, Object> dados;
    
    // getters e setters...
}
public class ConfiguracaoDTO {
    private Boolean validacaoRigida;
    private Boolean interromperNoErro;
    
    // getters e setters...
}

DTOs de Response

// DTO de resposta para criação de pedido
public class PedidoCriadoDTO {
    private Long numeroPedido;
    private LocalDate dataVencimento;
    private Double valorTotal;
    private String status;
    
    // getters e setters...
}
// DTO para listagem de produtos
public class ProdutoDTO {
    private Long id;
    private String nome;
    private String categoria;
    private Double preco;
    private Boolean ativo;
    
    // getters e setters...
}
// DTO para buscar usuário
public class UsuarioDTO {
    private Long id;
    private String nome;
    private String email;
    private String perfil;
    
    // getters e setters...
}
// DTO de resposta para processamento de lote
public class ResultadoDTO {
    private String loteId;
    private Integer totalProcessados;
    private Integer sucessos;
    private Integer erros;
    private List<DetalheProcessamentoDTO> detalhes;
    
    // getters e setters...
}
public class DetalheProcessamentoDTO {
    private Integer registro;
    private String status;
    private Long id;
    private String mensagem;
    
    // getters e setters...
}

🔄 Exemplo de Requisição HTTP

📋 Formato Padrão da Request

O JSON da request deve seguir o padrão da API legada do Sankhya:

{
  "serviceName": "NomeDoServico.nomeDoMetodo",
  "requestBody": {
    "nomeParametro1": { /* objeto correspondente ao DTO */ },
    "nomeParametro2": { /* objeto correspondente ao DTO */ }
  }
}

Regras importantes:

  • serviceName: Nome completo do serviço e método (ex: PedidoServiceSP.criarPedido)
  • requestBody: Contém os argumentos do método como propriedades nomeadas
  • Cada argumento: É mapeado pelo nome do parâmetro no método Java

Exemplo de método:

public PedidoCriadoDTO criarPedido(PedidoDTO pedido) {
    // O parâmetro "pedido" será mapeado para "pedido" no requestBody
}

🚀 Exemplo Prático de Requisição

URL: .../mge/service.sbr?serviceName=PedidoServiceSP.criarPedido
Método: POST

Content-Type: application/json

Request Body (correspondente ao método criarPedido(PedidoDTO pedido)):

{
  "serviceName": "PedidoServiceSP.criarPedido",
  "requestBody": {
    "pedido": {
      "cliente": {
        "codigo": 12345,
        "nome": "João Silva"
      },
      "itens": [
        {
          "produtoId": 100,
          "quantidade": 2,
          "valorUnitario": 150.50
        },
        {
          "produtoId": 101,
          "quantidade": 1,
          "valorUnitario": 250.00
        }
      ],
      "observacao": "Entrega urgente"
    }
  }
}

📤 Exemplo de Resposta

📋 Formato Padrão da Response

A resposta segue o envelope padrão da API legada do Sankhya:

{
  "serviceName": "NomeDoServico.nomeDoMetodo",
  "status": "1",              // "1" = Sucesso, "0" = Erro, "3" = Timeout, "4" = Cancelado
  "pendingPrinting": "false",
  "transactionId": "HASH_DA_TRANSACAO",
  "responseBody": { /* objeto retornado pelo método */ }
}

✅ Response de Sucesso

Response Status: 200 OK

Content-Type: application/json

Response Body (correspondente ao PedidoCriadoDTO retornado):

{ 
  "serviceName": "PedidoServiceSP.criarPedido",
  "status": "1",
  "pendingPrinting": "false",
  "transactionId": "CB0F625A72C214CF8449F0B18E1FA81A",
  "responseBody": {
    "numeroPedido": 987654,
    "dataVencimento": "2024-12-31",
    "valorTotal": 551.00,
    "status": "PENDENTE"
  }
}

❌ Response de Erro

Quando status é diferente de "1", indica erro:

  • "0" - Erro de execução
  • "3" - Timeout
  • "4" - Serviço cancelado por concorrência
{ 
  "serviceName": "PedidoServiceSP.criarPedido",
  "status": "0",
  "pendingPrinting": "false",
  "transactionId": "CB0F625A72C214CF8449F0B18E1FA81A",
  "statusMessage": "Erro de validação: O campo nome é obrigatório;\n\t- O campo descricao é obrigatório"
}

Observação: Em caso de erro, o campo responseBody não é incluído, e a mensagem de erro fica em statusMessage.


Exemplos Adicionais

🧩 Exemplo Completo: Serviço de Consulta

@Service(serviceName = "ConsultaServiceSP", transactionType = TransactionType.NotSupported)
public class ConsultaService {

    public List<ProdutoDTO> listarProdutos(FiltroDTO filtro) {
        // Lógica de consulta
        return produtoRepository.buscarPorFiltro(filtro);
    }
}

Requisição:

POST /mge/service.sbr?serviceName=ConsultaServiceSP.listarProdutos

**Request Body** (correspondente ao método `listarProdutos(FiltroDTO filtro)`):
```json
{
  "serviceName": "ConsultaServiceSP.listarProdutos",
  "requestBody": {
    "filtro": {
      "categoria": "ELETRONICOS",
      "ativo": true,
      "limite": 10
    }
  }
}

Response (correspondente a List<ProdutoDTO> retornado):

{
  "serviceName": "ConsultaServiceSP.listarProdutos",
  "status": "1",
  "pendingPrinting": "false",
  "transactionId": "CB0F625A72C214CF8449F0B18E1FA81A",
  "responseBody": [
    {
      "id": 100,
      "nome": "Notebook Dell",
      "categoria": "ELETRONICOS",
      "preco": 2500.00,
      "ativo": true
    },
    {
      "id": 101,
      "nome": "Mouse Logitech",
      "categoria": "ELETRONICOS",
      "preco": 85.50,
      "ativo": true
    }
  ]
}

📋 Exemplo com Diferentes Tipos de Request

🔍 GET com Parâmetros Simples

Para métodos com parâmetros simples (tipos primitivos), você pode usar tanto o formato completo quanto parâmetros na URL:

@Service(serviceName = "UsuarioServiceSP")
public class UsuarioService {
    
    public UsuarioDTO buscarPorId(Long id) {
        return usuarioRepository.findById(id);
    }
}

Request Body (correspondente ao método buscarPorId(Long id)):

{
  "serviceName": "UsuarioServiceSP.buscarPorId",
  "requestBody": {
    "id": 123
  }
}

Response (correspondente ao UsuarioDTO retornado):

{
  "serviceName": "UsuarioServiceSP.buscarPorId",
  "status": "1",
  "pendingPrinting": "false",
  "transactionId": "CB0F625A72C214CF8449F0B18E1FA81A",
  "responseBody": {
    "id": 123,
    "nome": "Maria Santos",
    "email": "[email protected]",
    "perfil": "ADMINISTRADOR"
  }
}

📦 POST com Body Complexo

@Service(serviceName = "ProcessamentoServiceSP")
public class ProcessamentoService {
    
    @Transactional
    public ResultadoDTO processarLote(LoteDTO lote) {
        // Processa múltiplos registros
        return processadorService.processar(lote);
    }
}

Request Body (correspondente ao método processarLote(LoteDTO lote)):

{
  "serviceName": "ProcessamentoServiceSP.processarLote",
  "requestBody": {
    "lote": {
      "identificador": "LOTE_001",
      "registros": [
        {
          "tipo": "CLIENTE",
          "dados": {
            "nome": "Empresa ABC",
            "cnpj": "12.345.678/0001-90"
          }
        },
        {
          "tipo": "PRODUTO",
          "dados": {
            "nome": "Produto XYZ",
            "categoria": "SERVICOS"
          }
        }
      ],
      "configuracao": {
        "validacaoRigida": true,
        "interromperNoErro": false
      }
    }
  }
}

Response (correspondente ao ResultadoDTO retornado):

{
  "serviceName": "ProcessamentoServiceSP.processarLote",
  "status": "1",
  "pendingPrinting": "false",
  "transactionId": "CB0F625A72C214CF8449F0B18E1FA81A",
  "responseBody": {
    "loteId": "LOTE_001",
    "totalProcessados": 2,
    "sucessos": 1,
    "erros": 1,
    "detalhes": [
      {
        "registro": 1,
        "status": "SUCESSO",
        "id": 45678
      },
      {
        "registro": 2,
        "status": "ERRO",
        "mensagem": "Categoria 'SERVICOS' não encontrada"
      }
    ]
  }
}

🔄 Método com Múltiplos Parâmetros

@Service(serviceName = "RelatorioServiceSP")
public class RelatorioService {
    
    public RelatorioDTO gerarRelatorio(FiltroDTO filtros, ConfiguracaoDTO config, String formato) {
        // Gera relatório com base nos parâmetros
        return relatorioGenerator.gerar(filtros, config, formato);
    }
}

Request Body (correspondente ao método gerarRelatorio(FiltroDTO filtros, ConfiguracaoDTO config, String formato)):

{
  "serviceName": "RelatorioServiceSP.gerarRelatorio",
  "requestBody": {
    "filtros": {
      "categoria": "VENDAS",
      "ativo": true,
      "limite": 100
    },
    "config": {
      "validacaoRigida": false,
      "interromperNoErro": true
    },
    "formato": "PDF"
  }
}

🔗 Correlação DTO ↔ JSON

Para facilitar o entendimento, observe como cada campo do DTO se mapeia para o JSON:

Exemplo: PedidoDTO ↔ Request JSON

// Método Java
public PedidoCriadoDTO criarPedido(PedidoDTO pedido) { ... }

// Estrutura do DTO
public class PedidoDTO {
    private ClienteDTO cliente;      // → "pedido.cliente": { ... }
    private List<ItemPedidoDTO> itens; // → "pedido.itens": [ ... ]
    private String observacao;       // → "pedido.observacao": "..."
}

Exemplo: PedidoCriadoDTO ↔ Response JSON

// Retorno do método
public class PedidoCriadoDTO {
    private Long numeroPedido;       // → "responseBody.numeroPedido": 987654
    private LocalDate dataVencimento; // → "responseBody.dataVencimento": "2024-12-31"
    private Double valorTotal;       // → "responseBody.valorTotal": 551.00
    private String status;           // → "responseBody.status": "PENDENTE"
}

💡 Dicas Importantes

O que o SDK faz automaticamente:

  • Conversão de tipos: String ↔ LocalDate, Integer ↔ Long, BigDecimal ↔ Double, etc.
  • Serialização/deserialização: Objetos aninhados, arrays e coleções
  • Mapeamento de parâmetros: Nome dos parâmetros Java ↔ propriedades JSON
  • Envelope de resposta: Encapsula retorno no formato padrão Sankhya

⚠️ Pontos de atenção:

  • Nome dos parâmetros: Os nomes dos parâmetros no método Java devem corresponder às chaves no requestBody
  • Tipos simples vs. complexos: Para tipos simples (String, Integer, Long), você pode usar parâmetros na URL, mas é recomendado usar sempre o formato JSON completo
  • Valores nulos: Campos nulos no DTO não aparecem no JSON de resposta
  • Datas: LocalDate é serializado como "YYYY-MM-DD", LocalDateTime como "YYYY-MM-DDTHH:mm:ss"

🔧 Debugging:

  • Logs: Use o transactionId para rastrear requisições nos logs
  • Status codes: Sempre verifique o campo status na resposta
  • Mensagens de erro: Em caso de erro, a mensagem detalhada fica em statusMessage

🎯 Resumo

A anotação @Service do SDK Sankhya moderniza a criação de endpoints, oferecendo:

  1. 📝 Sintaxe Declarativa: Apenas anote a classe e defina métodos públicos
  2. 🔄 Conversão Automática: JSON ↔ DTOs sem código boilerplate
    3📡 Formato Padrão: Compatível com a API legada Sankhya
    4🧪 Testabilidade: Classes POJO facilmente testáveis