Este projeto foi desenvolvido como parte de um Hackathon para modernizar uma regra de validação de crédito oriunda de um ambiente legado RPG/AS/400 para uma API moderna em Java 21.
A ideia é demonstrar como uma regra antiga, tradicionalmente mantida em linguagem RPG Free, pode ser transformada em uma solução prática, evolutiva e reutilizável sob o padrão de Clean Architecture e Governança Agêntica (MCP), integrando:
- Java 21 (LTS)
- Spring Boot 3
- JPA/Hibernate
- SQLite (Banco local)
- API RESTful (OpenAPI/Swagger)
- Appwrite Cloud para serviços de integração externa e nuvem
A aplicação original simula a validação de crédito de clientes considerando os limites físicos e regras de negócio do mainframe. No entanto, migrações sintáticas comuns de código costumam falhar por não identificarem a lógica procedural de negócio implícita no fluxo do RPG:
- A Regra Oculta do RPG (
SITCLI = 'I'): No programa procedural legado (CONCLI.RPGLE), existe uma verificação de segurança: se a situação cadastral do cliente for inativa (SITCLI = 'I'), a liberação do crédito é rejeitada imediatamente (pAprovado = *off), abortando o processo antes de validar o limite matemático (wSomaPedidos < limCred). [81, 82] - A Solução Java: Essa validação de negócio foi portada de forma isolada e elegante na camada de serviços (
ClienteService.java), lançando uma exceção personalizada (ClienteInativoException) que impede o avanço de requisições de clientes suspensos.
Para garantir que o buffer de dados e os comprimentos definidos no DDS original (CLIENTES.PF) sejam rigorosamente mantidos sem riscos de overflow ou perda de precisão decimal, o mapeamento para as anotações do Jakarta Validation no Java 21 segue a especificação técnica abaixo: [80]
| Campo Legado | Tipo DDS (Original) | Campo Java Moderno | Tipo Java 21 | Validação Jakarta / JPA | Descrição |
|---|---|---|---|---|---|
| CODCLI | Packed (6, 0) | codigo |
Long |
@Max(999999) |
Código de identificação do cliente (máximo 6 dígitos) [80] |
| NOMECLI | Character (40) | nome |
String |
@Size(max = 40) |
Nome completo do cliente [80] |
| CPFCLI | Character (11) | cpf |
String |
@Size(max = 11) |
CPF do cliente (formato e tamanho exato) [80] |
| LIMCRED | Packed (9, 2) | limiteCredito |
BigDecimal |
@Digits(integer = 7, fraction = 2) |
Limite de crédito aprovado (precisão exata) [80] |
| SITCLI | Character (1) | situacao |
String |
@Size(max = 1) |
Situação cadastral ('A' = Ativo, 'I' = Inativo) [80] |
// Busca cliente no DB2 por chave primária
chain pCodigo clientes clienteDS;
if %found(clientes);
pNome = %trim(nomecLi);
pLimite = limCred;
// REGRA OCULTA: Cliente inativo não possui aprovação
if sitCli = 'I';
pAprovado = *off;
else;
// Validação do limite matemático com soma de pedidos pendentes
if wSomaPedidos < limCred;
pAprovado = *on;
else;
pAprovado = *off;
endif;
endif;
else;
pNome = 'NAO ENCONTRADO';
pAprovado = *off;
endif;
``` [81, 82]
### Modernização em Java 21 (`ClienteService.java`)
```java
@Service
public class ClienteService {
@Autowired
private ClienteRepository repository;
@Autowired
private AppwriteIntegrationService appwriteService;
public ClienteDTO consultarEValidarLimite(Long codigo, BigDecimal somaPedidos) {
// 1. Busca o cliente na base de dados
Cliente cliente = repository.findById(codigo)
.orElseThrow(() -> new ResourceNotFoundException("Cliente não cadastrado no sistema."));
// 2. Regra oculta de inatividade portada do RPG
if ("I".equalsIgnoreCase(cliente.getSituacao())) {
throw new ClienteInativoException("Limite rejeitado: O cliente encontra-se INATIVO.");
}
// 3. Validação matemática do limite com alta precisão decimal
boolean aprovado = somaPedidos.compareTo(cliente.getLimiteCredito()) < 0;
// 4. Registro/Auditoria externa na nuvem (Appwrite)
appwriteService.registrarConsultaCredito(cliente.getCodigo(), aprovado);
return new ClienteDTO(
cliente.getCodigo(),
cliente.getNome(),
cliente.getLimiteCredito(),
aprovado,
cliente.getSituacao()
);
}
}- Java 21 (LTS): Uso de Records para imutabilidade e alta performance de DTOs.
- Spring Boot 3.3.x: Endpoints sob arquitetura REST, injeção de dependência nativa e tratamento global de erros.
- Spring Data JPA & Hibernate: Desacoplamento e abstração da persistência de dados.
- SQLite: Persistência relacional local simples de inicializar.
- Appwrite Cloud: SDK integrado para auditorias e sincronização externa de dados. [62]
- Clean Architecture / MVC: Estrutura organizada com divisão clara de atribuições (
controller,service,repository,model/entity). [75] - Segurança por Design: Chaves e dados sensíveis de integração com o Appwrite Cloud injetados dinamicamente via variáveis de ambiente (
${APPWRITE_API_KEY}), sem dados fixos (hardcoded) no código. [67, 75]
Hackathon-rpg-java/
├── src/
│ └── main/
│ ├── java/
│ │ └── com/ibm/modernizacao/
│ │ ├── config/
│ │ │ └── AppwriteConfig.java
│ │ ├── controller/
│ │ │ └── ClienteController.java
│ │ ├── exception/
│ │ │ ├── ClienteInativoException.java
│ │ │ └── GlobalExceptionHandler.java
│ │ ├── model/
│ │ │ ├── dto/
│ │ │ │ └── ClienteDTO.java
│ │ │ └── entity/
│ │ │ └── Cliente.java
│ │ ├── repository/
│ │ │ └── ClienteRepository.java
│ │ ├── service/
│ │ │ ├── AppwriteIntegrationService.java
│ │ │ └── ClienteService.java
│ │ └── ValidacaoCreditoApplication.java
│ └── resources/
│ └── application.properties
├── AGENTS.md # Briefing de governança e comportamento do agente de IA [75]
├── mcp.json # Configuração de conexão dos servidores MCP locais [4]
├── schema.sql
├── pom.xml
└── README.md
Antes de rodar o projeto, certifique-se de ter instalado:
- Java 21 (LTS) [94]
- Maven
- Git
git clone <url-do-repositorio>
cd Hackathon-rpg-javajava -versionA versão esperada é Java 21. [95]
Antes de iniciar a aplicação, configure as variáveis de ambiente necessárias para a integração segura com a nuvem do Appwrite. Nunca versione chaves de API no repositório: [67]
export APPWRITE_ENABLED="true" # ativa a integração com a nuvem
export SUPABASE_URL="https://jqdbscialntgtmdhkmpz.supabase.co"
export SUPABASE_SERVICE_ROLE="COLE_SUA_CHAVE_SUPABASE_AQUI (gerada no painel em Settings -> API)"Sem o
collectionIde aapiKey, a aplicação funciona apenas com o fallback SQLite local e recusa operações na nuvem de forma deliberada.
mvn clean installmvn spring-boot:runApós o boot da aplicação, a API estará disponível em: http://localhost:8082 (configurado em application.properties).
Endpoints:
| Método | Rota | Descrição |
|---|---|---|
GET |
/api/appwrite/status |
Verifica a conexão com o Appwrite Cloud (somente leitura) |
GET |
/api/clientes/{codigo} |
Consulta cliente no SQLite local (fallback) |
POST |
/api/clientes |
Cria cliente (nuvem se APPWRITE_ENABLED=true, senão SQLite) |
PUT |
/api/clientes/{codigo} |
Atualiza cliente (nuvem se APPWRITE_ENABLED=true, senão SQLite) |
Exemplo de consulta:
http://localhost:8082/api/clientes/100?somaPedidos=500
Exemplo de criação/atualização:
curl -X PUT http://localhost:8082/api/clientes/1 \
-H "Content-Type: application/json" \
-d '{"nome":"Cliente Nuvem","cpf":"12345678901","limite":5000.00,"situacao":"A"}'O projeto utiliza SQLite e o schema inicial está em:
schema.sql
``` [6]
A configuração do banco está em:
```text
src/main/resources/application.properties
A persistência principal usa SQLite local (fallback). Quando APPWRITE_ENABLED=true, as operações de criação (POST) e atualização (PUT) de clientes são persistidas no Appwrite Cloud via REST, usando os endpoints oficiais de documentos:
GET /databases/{db}/collections/{collection}/documents— listarPOST /databases/{db}/collections/{collection}/documents— criar (documentId+data)PATCH /databases/{db}/collections/{collection}/documents/{documentId}— atualizar
O documentId usado é o codcli do cliente. Os atributos gravados são os campos equivalentes ao DDS legado: codcli, nomecli, cpfcli, limcred e sitcli.
Endpoint de leitura que valida project/database/collection/API key sem modificar dados. Faça um teste de conexão antes de operar na nuvem.
200com"conexao": "OK"→ IDs e chave corretos (retornatotalDocumentos).502com"conexao": "ERRO"→ ID(s) incorreto(s) ou falta de permissão; o corpo traz o motivo.503com"Appwrite desabilitado..."→APPWRITE_ENABLEDnão estátrue.
Respostas úteis de erro do Appwrite: 404 resource not found indica project/database/collection incorretos; 401/403 indica API key inválida ou sem o scope necessário.
- O banco SQLite será inicializado localmente no diretório do projeto.
- O arquivo
schema.sqljá foi configurado para evitar erros de criação repetida da tabela quando a aplicação é iniciada diversas vezes. - O projeto pode ser expandido para integrar autenticação e outros serviços do Appwrite conforme a evolução do Hackathon. [62]
Para complementar o ciclo de vida deste projeto legado, as próximas fases técnicas preveem a integração direta com o ambiente físico IBM i público do PUB400.com:
- Usuário Reservado: Perfil
BRASIL01criado e aguardando provisionamento. - Biblioteca de Fontes (
BRASIL011): Área dedicada no servidor para armazenamento dos fontes físicos legados em pastas dedicadas do sistema de arquivos integrado (IFS) ou membros clássicos (QDDSRCeQRPGLESRC). - Biblioteca de Objetos (
BRASIL012): Biblioteca alvo das compilações físicas do arquivo físico (CLIENTES.PF) e do programa executável RPG (CONCLI.RPGLE). - Governança VS Code: Toda a manipulação de membros e execução de comandos CL será realizada via conexão SSH usando a extensão Code for IBM i, dispensando o uso do emulador 5250 ("tela verde").
- Ativar a integração em tempo real com o PUB400.com usando o SDK Java do IBM i.
- Adicionar autenticação federada com Appwrite.
- Criar testes unitários e de integração utilizando JUnit 5 e Mockito.
- Expor mais endpoints REST para criação e atualização cadastral de clientes.
- Incluir regras de negócio mais sofisticadas originárias do legado.
Este projeto foi desenvolvido como material de estudo e prova de conceito para modernização de aplicações legadas IBM i em Java + Spring Boot utilizando Governança Agêntica (MCP). [248]