Choosing Extension Patterns: In-App vs Side-by-Side under Clean Core
How to decide between in-app key-user extensibility, developer extensibility, and side-by-side extensions on BTP, and how that choice affects integration flow and long-term maintainability.
Explanation
Clean Core adoption is not a single switch; it is a set of recurring architectural decisions made every time a business requirement cannot be satisfied by standard SAP functionality. The central question an architect must answer for each gap is: where should this logic live, and through what interface should it talk to the core? In S/4HANA on-premise and private cloud, SAP offers a layered extensibility model. In-app extensibility using key-user tools (custom fields, custom logic via released Business Add-Ins, custom CDS views built on released objects) keeps changes inside the ABAP system but constrained to supported extension points. Developer extensibility (ABAP Cloud, RAP - RESTful ABAP Programming model) allows more custom ABAP code but still requires using only released APIs and CDS views, not direct table access or core object modification. Side-by-side extensibility moves the custom logic entirely off the core system, typically onto SAP BTP, using ABAP Environment, Java, or other runtimes, communicating with the core exclusively through released APIs (OData, SOAP, REST) or event-driven integration via an event mesh or similar broker. In S/4HANA Public Cloud, the constraint is stricter: only in-app extensibility using key-user tools and the released extensibility scope is possible for logic that must run inside the tenant; anything more complex must be side-by-side by design, since public cloud tenants do not permit classic ABAP development outside the governed extensibility framework. The decision criteria an architect should apply, in rough priority order: (1) Can standard configuration or a released BAdI/enhancement spot satisfy the requirement without custom code? Prefer this always. (2) Does the logic need tight, low-latency coupling with core transactions (e.g., pricing routines invoked mid-document)? This often favors in-app developer extensibility using RAP and released APIs, because side-by-side calls introduce network latency and eventual-consistency risk. (3) Is the logic long-lived, high-change-frequency, or does it need independent release cycles, its own scaling, or non-ABAP skills (e.g., machine learning scoring, complex UI)? This favors side-by-side on BTP. (4) Does the requirement span multiple SAP and non-SAP systems (orchestration, data aggregation, workflow across systems)? This is a strong signal for side-by-side, often using an integration layer like SAP Integration Suite rather than embedding orchestration logic in any single core system. Runtime and integration flow differ meaningfully between the two patterns. In-app RAP-based extensions execute in the same database and transaction context as standard logic, so they can participate in the same commit and rollback boundaries, and they read/write only through released CDS views and APIs, never raw core tables. Side-by-side extensions on BTP call into the core via API endpoints exposed through the API Business Hub / released interfaces, typically authenticated via OAuth2 with client credentials or principal propagation, and any transactional consistency across the boundary must be designed explicitly, since a side-by-side write and a core write are not part of one database transaction. This introduces the need for idempotent design, retry logic, and sometimes compensating transactions if a downstream call fails after the core commit. A common real-world pattern is hybrid: use in-app custom fields and key-user logic for minor calculations tightly bound to a document, and side-by-side apps on BTP for cross-system processes, dashboards, or logic requiring frequent independent releases. Getting this split wrong is the most frequent Clean Core governance failure: teams either over-extend the core because side-by-side setup feels heavier, or over-engineer side-by-side solutions for simple field-level logic that in-app tools could have solved in an hour.
Code example
* Example: released API check before choosing side-by-side* Pseudo-decision checklist embedded as ABAP comment for governance review "1. Is there a standard config option? -> if yes, STOP, no extension needed"2. Is there a key-user extensibility option (custom field/logic)? -> use in-app"3. Does logic need same-transaction consistency with core commit? -> use RAP developer extensibility, released APIs only"4. Is logic cross-system, independently released, or non-ABAP? -> use side-by-side on BTP * RAP-based in-app extension skeleton (illustrative only)CLASS zcl_bp_extend_pricing DEFINITION. PUBLIC SECTION. INTERFACES if_ex_pricing_bAdI. " released BAdI interface, not direct table accessENDCLASS.CLASS zcl_bp_extend_pricing IMPLEMENTATION. METHOD if_ex_pricing_bAdI~adjust_price. " only calls released CDS view / API, never SE16-style raw table read lv_discount = get_discount_from_released_api( iv_customer ). ENDMETHOD.ENDCLASS.Real project scenario
During an S/4HANA private cloud migration, a retail customer needed a custom loyalty discount calculation. The initial proposal embedded the logic directly in a modified pricing routine, touching core tables. The architecture review board rejected it under Clean Core policy and instead split the requirement: a released pricing BAdI held a thin in-app extension for the same-transaction discount lookup, while the loyalty point accrual and cross-channel reporting were built as a side-by-side BTP application consuming released OData services asynchronously via events, avoiding any core modification.
Common mistakes
• Choosing side-by-side for simple field-level logic that key-user tools could handle, adding unnecessary integration overhead • Embedding cross-system orchestration logic inside in-app extensions, creating hidden dependencies that break clean upgrade paths • Assuming side-by-side calls are transactionally consistent with the core commit without designing for eventual consistency • Using non-released APIs or direct table access from a side-by-side extension because a released equivalent was not readily discovered • Failing to document which extensibility tier was chosen and why, causing repeated re-litigation of the same decision by different teams
Best practices
• Always check for standard configuration and released enhancement spots before proposing any custom extension • Document the extensibility tier decision and rationale for every custom requirement in an architecture decision log • Reserve in-app developer extensibility for logic requiring same-transaction consistency with core documents • Design side-by-side integrations for idempotency and failure recovery since they are not part of the core's database transaction • Differentiate guidance by deployment model: public cloud restricts in-app options more than private cloud or on-premise
Interview angle
Interviewers probe whether candidates can articulate concrete decision criteria rather than reciting 'clean core is good.' Expect scenario questions like 'a custom discount rule needs same-transaction consistency—in-app or side-by-side?' A strong answer names the extensibility tiers, the released-API constraint, and the transactional consistency trade-off, and explicitly differentiates public cloud (in-app only, side-by-side for the rest) from private cloud/on-premise (in-app developer extensibility with RAP also possible).