ADR-012: ADService Facade Architecture
Status
Implementing (v1.27.0)
Context
The CLI needs to perform Application Dictionary operations that can output to multiple targets:
- REST API - Direct calls to iDempiere server for immediate changes
- SQL Migration Scripts - Generate PostgreSQL/Oracle DDL for version control
- 2Pack XML - Export for distribution and deployment
Previously, these were scattered across:
IdempiereApiClient- REST operations with JSON→POJO mappingMigrationService- SQL script generationPackOutService- 2Pack XML generation- Various commands calling these directly
This created inconsistency and made it hard to:
- Use the same business logic across output targets
- Validate AD changes using iDempiere core logic
- Switch between output modes easily
Decision
Create a unified ADService facade that abstracts Application Dictionary operations behind a single interface with multiple implementations:
┌─────────────────────────────────────────────────────────────────┐
│ CLI Commands │
└─────────────────────────┬───────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ ADService (Interface) │
│ - createTable(), createColumn(), etc. │
│ - Unified API for all AD operations │
└─────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│RestADService│ │SqlADService │ │PackOutAD │
│ │ │ │ │Service │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ POST/PUT to │ │ Generate │ │ Generate │
│ /models/* │ │ DDL scripts │ │ PIPO2 XML │
└─────────────┘ └─────────────┘ └─────────────┘
Interface Design
public interface ADService {
// Table operations
ADTable createTable(ADTable table);
ADTable updateTable(ADTable table);
ADTable getTableByName(String tableName);
List<ADTable> getTablesByPattern(String pattern, String entityType);
// Column operations
ADColumn createColumn(ADColumn column);
List<ADColumn> getColumns(int tableId);
// Window/Tab/Field operations
ADWindow createWindow(ADWindow window);
ADTab createTab(ADTab tab);
ADField createField(ADField field);
// Process operations
ADProcess createProcess(ADProcess process);
ADProcessPara createProcessParameter(ADProcessPara param);
// Menu operations
ADMenu createMenu(ADMenu menu);
ADTreeNodeMM createTreeNode(ADTreeNodeMM node);
// Reference operations
ADReferenceRecord createReference(ADReferenceRecord reference);
ADRefList createRefList(ADRefList refList);
ADRefTable createRefTable(ADRefTable refTable);
// Synchronization
void synchronizeTable(int tableId);
void synchronizeColumn(int columnId);
// Output control
void flush();
OutputType getOutputType();
enum OutputType { REST, SQL, PACK_OUT }
}
Implementation Classes
| Class | Output | Description |
|---|---|---|
RestADService |
REST API | Delegates to IdempiereApiClient, immediate execution |
SqlADService |
SQL files | Generates DDL, tracks changes in memory, writes on flush() |
PackOutADService |
2Pack XML | Buffers changes, generates PIPO2 XML on flush() |
Key Benefits
- Single API - Commands use one interface regardless of output target
- Validation Consistency - Core logic can be shared across implementations
- Testability - Easy to mock ADService in unit tests
- Flexibility - Add new output types (e.g., JSON, YAML) without changing commands
- Batch Operations - SQL/2Pack buffer changes, REST executes immediately
Output Type Selection
Commands will support --output flag:
# Default: REST API (immediate)
idempiere-cli add table CLD_MyTable
# Generate SQL migration scripts
idempiere-cli add table CLD_MyTable --output sql
# Generate 2Pack XML
idempiere-cli add table CLD_MyTable --output 2pack
Or via configuration:
idempiere.cli.output=sql
Consequences
Positive
- Unified Interface - Single point of entry for all AD operations
- Multi-Output - Same operation can target REST, SQL, or 2Pack
- Validation Reuse - iDempiere core logic used consistently
- Better Testing - Interface-based design enables mocking
- Extensibility - Easy to add new output implementations
Negative
- Abstraction Overhead - Additional layer between commands and implementation
- State Management - SQL/2Pack need to buffer changes before flush
- Read Operations - SQL/2Pack can't read from database (write-only)
Mitigation
- SQL/2Pack implementations throw for read operations or delegate to REST
- Use
GeneratedOpenApiFactoryfor non-AD REST operations (health, cache, workflow, etc.)
IdempiereApiClient Deprecation
Decision: The hand-written IdempiereApiClient class will be removed and replaced by:
- ADService facade - For all Application Dictionary operations (tables, columns, windows, processes, etc.)
- GeneratedOpenApiFactory - For non-AD REST operations (health, cache, workflow, servers, etc.)
Rationale
| Before | After | Reason |
|---|---|---|
IdempiereApiClient.getTable() |
ADService.getTableByName() |
Unified facade with multi-output |
IdempiereApiClient.createColumn() |
ADService.createColumn() |
Can output to REST/SQL/2Pack |
IdempiereApiClient.getHealth() |
GeneratedOpenApiFactory.healthApi() |
OpenAPI-generated typed client |
IdempiereApiClient.getCaches() |
GeneratedOpenApiFactory.cacheApi() |
OpenAPI-generated typed client |
Migration Path
RestADService- Inline HTTP client logic (no longer wraps IdempiereApiClient)- Commands using AD operations → Use
ADService - Commands using non-AD operations → Use
GeneratedOpenApiFactory - Delete
IdempiereApiClientclass
Implementation Plan
Phase 1: Core Interface (v1.27.0) ✅ COMPLETE
- [x] Create
ADServiceinterface with all AD operations - [x] Create
RestADServicewrappingIdempiereApiClient - [x] Create
ADServiceExceptionfor error handling - [x] Create
SqlADServicefor migration script generation - [x] Create
PackOutADServicefor 2Pack XML generation
Phase 2: IdempiereApiClient Removal (v1.27.0)
- [ ] Refactor
RestADServiceto use direct HTTP (inline from IdempiereApiClient) - [ ] Migrate commands to use
ADServiceorGeneratedOpenApiFactory - [ ] Delete
IdempiereApiClientclass - [ ] Update tests
Phase 3: Command Integration (v1.28.0)
- [ ] Add
--outputflag to relevant commands - [ ] Create
ADServiceFactoryfor implementation selection - [ ] Refactor
addcommands to use ADService - [ ] Refactor
generatecommands to use ADService
Phase 4: Validation (Future)
- [ ] Integrate iDempiere core validation (MColumn, MTable)
- [ ] Add pre-create validation hooks
- [ ] Add cross-reference validation
File Structure
src/main/java/org/idempiere/cli/services/ad/
├── ADService.java # Interface
├── ADServiceException.java # Exception
├── ADServiceFactory.java # Factory for implementations
├── RestADService.java # REST implementation
├── SqlADService.java # SQL migration implementation
└── PackOutADService.java # 2Pack XML implementation