Skip to content

refactor(ai-core): migrate AICore from CqnService plugin to RemoteService & remove delegation handler #72

@Schmarvinius

Description

@Schmarvinius

Context

The AICore service is currently implemented as a plain CqnService registered via CdsRuntimeConfiguration SPI, with an AICoreApplicationServiceHandler ("delegation handler") that intercepts CRUD events on all application services to automatically forward queries targeting projections on AICore.* entities.

This should be migrated to the standard RemoteService pattern used by CAP Java for services backed by external systems (see cds-services-impl RemoteServiceImpl / AbstractCdsDefinedService).

Problems with the Current Approach

Delegation Handler (AICoreApplicationServiceHandler)

The delegation handler at cds-feature-ai-core/.../handler/AICoreApplicationServiceHandler.java introduces significant issues:

  1. Wildcard registration@ServiceName(value = "*", type = ApplicationService.class) intercepts every single CRUD event for every entity across the entire application
  2. Runtime check overhead — Every request must resolve whether the entity is a projection on AICore.* before deciding to act or return
  3. Incomplete coverage — Only handles READ/CREATE/UPDATE/DELETE; actions (like stop()) are not forwarded
  4. Handler ordering conflicts@HandlerOrder(OrderConstants.On.FEATURE) competes with other feature-level handlers (e.g., change-tracking, audit-log)
  5. Fragile CQN rewriting — The entityModifier pattern rewrites only the root segment; nested associations, expands, and complex projections (multi-level, renamed elements) can break silently
  6. Non-obvious "magic" — Developers don't see explicit forwarding in their code, making debugging difficult when things go wrong
  7. Draft interaction — If someone creates a draft-enabled projection on AICore entities, the delegation handler doesn't account for active/draft entity pairs

Service Type Mismatch

The AICoreService extends CqnService directly but functionally acts as a remote service (it proxies REST API calls to SAP AI Core). By not using RemoteService, it misses:

  • Built-in projection resolution (AbstractCdsDefinedService.runProjected() uses ProjectionResolver to automatically resolve queries through projection layers)
  • Entity validation (ensures entities belong to the service before dispatching)
  • Standard service lifecycle integration (Before/On/After dispatching with proper ordering)
  • Outbox compatibility (outboxService.outboxed(remoteService))
  • Messaging integration (auto-register inbound handlers for declared events)
  • DI discoverability — Cannot be injected as @Autowired RemoteService with standard patterns

Proposed Changes

1. Make AICoreService extend RemoteService

public interface AICoreService extends RemoteService {
    String DEFAULT_NAME = "AICore";

    // Keep programmatic helpers on the same interface
    String resourceGroup();
    String deploymentId(String resourceGroupId, ModelDeploymentSpec spec);
    ApiClient inferenceClient(String resourceGroupId, String deploymentId);
}

2. Implementation extends RemoteServiceImpl (or equivalent base)

The implementation gains automatic projection resolution from AbstractCdsDefinedService:

  • Queries like Select.from("AICore.deployments") dispatched directly work unchanged
  • Queries through projections are resolved automatically via ProjectionResolver before handler dispatch

3. Delete AICoreApplicationServiceHandler (delegation handler)

Users who want to expose AICore entities via their own ApplicationService must write an explicit forwarding handler:

// User's own handler — explicit, debuggable, customizable
@Component
@ServiceName("MyAdminService")
public class AIForwardHandler implements EventHandler {

    @Autowired @Qualifier("AICore")
    AICoreService aiCore;

    @On(entity = "MyAdminService.Deployments")
    public void forwardDeploymentRead(CdsReadEventContext ctx) {
        ctx.setResult(aiCore.run(ctx.getCqn()));
    }
}

This is the standard CAP pattern for forwarding to remote services (see ForwardHandler in cds-services/integration-tests/remote/). It is explicit, type-safe, and allows custom logic (filtering, authorization, mapping).

4. Register the service as a RemoteService

Update AICoreServiceConfiguration.services() to register the service such that the CAP runtime recognizes it as a RemoteService in the ServiceCatalog.

5. Update entity handlers registration

DeploymentHandler, ResourceGroupHandler, ConfigurationHandler, and ActionHandler should be registered against the AICore service specifically (either via @ServiceName("AICore") or @ServiceName(value = "*", type = RemoteService.class) with AICore-type filtering), rather than being wired through the service impl directly.

Usage

Before (implicit delegation — just works but fragile)

// app/srv/admin-service.cds
service AdminService {
  entity Deployments as projection on AICore.deployments;
}

No Java code needed — the delegation handler magically forwarded queries.

After (explicit forwarding — standard CAP pattern)

// app/srv/admin-service.cds
service AdminService {
  entity Deployments as projection on AICore.deployments;
}
@Component
@ServiceName("AdminService")
public class AdminServiceHandler implements EventHandler {

    @Autowired @Qualifier("AICore")
    AICoreService aiCore;

    @On(entity = "AdminService.Deployments")
    public Result onReadDeployments(CdsReadEventContext ctx) {
        return aiCore.run(ctx.getCqn());
    }
}

Benefits

  • Standard CAP architecture — same pattern as S/4HANA, SuccessFactors, or any remote OData consumption
  • No wildcard handler intercepting all application services
  • Explicit control — consumers decide what to expose and how
  • Actions/functions can be forwarded selectively
  • Works with outbox, messaging, standard DI
  • Projection resolution handled by the framework, not custom code
  • Easier to test (mock the RemoteService interface)

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    Newai-corePull request that touch the ai-core module

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions