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:

  1. REST API - Direct calls to iDempiere server for immediate changes
  2. SQL Migration Scripts - Generate PostgreSQL/Oracle DDL for version control
  3. 2Pack XML - Export for distribution and deployment

Previously, these were scattered across:

This created inconsistency and made it hard to:

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

  1. Single API - Commands use one interface regardless of output target
  2. Validation Consistency - Core logic can be shared across implementations
  3. Testability - Easy to mock ADService in unit tests
  4. Flexibility - Add new output types (e.g., JSON, YAML) without changing commands
  5. 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

  1. Unified Interface - Single point of entry for all AD operations
  2. Multi-Output - Same operation can target REST, SQL, or 2Pack
  3. Validation Reuse - iDempiere core logic used consistently
  4. Better Testing - Interface-based design enables mocking
  5. Extensibility - Easy to add new output implementations

Negative

  1. Abstraction Overhead - Additional layer between commands and implementation
  2. State Management - SQL/2Pack need to buffer changes before flush
  3. Read Operations - SQL/2Pack can't read from database (write-only)

Mitigation

IdempiereApiClient Deprecation

Decision: The hand-written IdempiereApiClient class will be removed and replaced by:

  1. ADService facade - For all Application Dictionary operations (tables, columns, windows, processes, etc.)
  2. 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

  1. RestADService - Inline HTTP client logic (no longer wraps IdempiereApiClient)
  2. Commands using AD operations → Use ADService
  3. Commands using non-AD operations → Use GeneratedOpenApiFactory
  4. Delete IdempiereApiClient class

Implementation Plan

Phase 1: Core Interface (v1.27.0) ✅ COMPLETE

Phase 2: IdempiereApiClient Removal (v1.27.0)

Phase 3: Command Integration (v1.28.0)

Phase 4: Validation (Future)

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

References

Path: /docs/developers/architecture/idempiere-hub/012-adservice-facade-architecture