🎮 A Camada de Serviço (`@Service`)
🎮 A Camada de Serviço (@Service)
@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?
@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)
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
@ServiceA 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:
serviceName(Atributo Obrigatório): Define o nome do serviço que será registrado na plataforma. Por convenção, deve terminar com o sufixoSP(ex:PedidoServiceSP).- 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
transactionTypeA 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 {
// ...
}TransactionType | Descrição | Quando 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). |
RequiresNew | Sempre 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. |
NotSupported | Executa 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. |
Mandatory | Exige 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
@TransactionalVocê 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.NotSupportedpara 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
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
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
transactionIdpara rastrear requisições nos logs - Status codes: Sempre verifique o campo
statusna 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:
- 📝 Sintaxe Declarativa: Apenas anote a classe e defina métodos públicos
- 🔄 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
Updated 11 days ago
