In the ToB market, software product development is often haunted by the “customization” curse. Typically, customized development requires extensive modifications to the product’s source code to meet the specific needs of specific users, which severely corrodes the generality of the product code. If the relationship between customized development and standardized product development cannot be properly balanced, it may seriously slow down the overall progress of the company’s products. Since competitiveness at the business level largely stems from differentiation, high-value mid-to-high-end customers inevitably have a large number of customization requirements—requirements that can be hard to abstract into a standardized, configurable pattern. To minimize the cost of conducting customized development alongside base product R&D, ideally customization should not modify the base product’s code. However, under current software engineering theory and general-purpose frameworks, achieving this is fraught with difficulties or incurs very high costs. In this article, I analyze the technical reasons why customized development gets into trouble and introduce how the Nop platform leverages the principles of Reversible Computation to offer an innovative customization capability, enabling application-layer code to gain fully incremental (delta-based) customization without any special design (such as pre-abstracted extension interfaces). The delta customization code is completely independent of the base product code; customizing the base product or Nop platform functionality requires no changes to the original code.
For concrete customization examples, see the sample project nop-app-mall/app-mall-delta. For the theory of Reversible Computation, see Reversible Computation: Next-Generation Software Construction Theory.
I. The Predicament of Customized Development
Traditionally, we mainly adopt two technical approaches to address customized development:
1.1 Code Branching
The most common approach is to create a dedicated code branch for each customer and then periodically merge from the trunk. Based on our observations, once there are more than five branches, confusion arises easily—especially when the same team maintains multiple branches with major differences simultaneously—leading even to erroneous commits and releasing the wrong versions.
Base products are generally complex and have numerous dependencies. Each branch containing similar yet not identical copies of the base product’s code causes development environment maintenance costs to skyrocket. When issues arise and diagnosis is needed, it’s often hard to quickly determine whether the root cause lies in the modified base product code or in the newly developed customization code because the base product code has been frequently altered.
The base product typically has a large codebase, and the effort and expertise required to perform diff analysis during code synchronization are both significant. If a bug is fixed or new functionality is added in the base product, synchronizing these changes downstream often becomes a prolonged process that must be carried out by developers who clearly understand the reasons behind the changes. Conversely, when an excellent feature in a customization branch is to be reverse-extracted and merged back into the main trunk, peeling off general-purpose code from that customization branch is also quite complex. Base product code and bespoke customization code get entangled and intermingled, lacking clear formal boundaries, easily forming spaghetti dependencies that are hard to disentangle.
1.2. Configurability and Pluggability
Configurability and pluggability constitute the other major technical route for supporting customized development. A mature productized offering must be highly configurable, with a large number of customer demands abstracted into cross-combinations of configuration items. For demand variations that are hard to exhaustively enumerate upfront, if we can anticipate where changes will occur, we can reserve extension points (extension interfaces) in the base product and then inject special plugin implementations during customization.
The main problem with this approach is that predictions can be inaccurate. Especially when the product itself is immature, it’s possible that none of the anticipated variations occur, and changes happen in unanticipated places. This leads to an awkward situation: when we need extensibility the most to reduce product evolution costs, it may not even exist.
High flexibility typically comes with increased complexity and performance overhead at runtime. Some uncommon requirements may leave deep and disproportionate scars inside the base product, leaving later developers puzzled: why is there such a convoluted design here? The requirement is only one sentence—how does it translate into so many interfaces and implementation classes? If we consider specific business requirements as a logical path, then configurability amounts to embedding multiple logical paths into the product to form a crisscrossed network, controlled by numerous switches to enable specific path connections. Without global guiding principles and design planning, configuration itself easily becomes a new source of complexity—hard to understand and hard to reuse.
Based on existing software engineering theory, such as Software Product Line engineering, technical means to enhance software flexibility can be categorized into adaptation, replacement, and extension. They can all be seen as additions to the core architecture. However, customization is not always about adding new functionality; oftentimes it involves hiding or simplifying existing functionality. Current techniques struggle to efficiently achieve the goal of removing existing features.
II. Reversible Computation Theory
Upfront predictions are unreliable; ex-post separation is costly. If we want lightweight customized development, then ideally the customization code and the base product code should be physically separated and, without reserved interfaces, a general mechanism should enable pruning and extension of base product functionality. To achieve this goal, we need to revisit the theoretical foundations of customized development.
Assume we have built a base product X with multiple components, expressed as:
X = A + B + C
We aim to develop a target product Y, which also has multiple components:
Y = A + B + D
Developing product Y based on product X, at the abstract level, corresponds to establishing an operation from X to Y:
Y = A + B + D = (A + B + C) + (-C + D) = X + Delta
If we truly can avoid modifying the base product X, a natural theoretical conclusion is: customization code amounts to a Delta correction applied to the base product.
Furthermore, we can derive the following:
Some highly flexible SaaS products store form configurations and workflow configurations in database tables and achieve customized development per specific users by adjusting configurations. In this approach, the configuration tables and the primary keys of configuration items essentially form a coordinate system. Based on this, one can add version fields to configuration items to enable version management and even version inheritance.
Hot patch mechanisms offered by some software are essentially a Delta correction mechanism. Successful patch application relies on coordinate system positioning provided at the infrastructure level and a delta merge algorithm executed after locating. However, compared to customized development, hot updates impose lower structural requirements on patches: patches need not have relatively stable business semantics, and they may not correspond to source code directly understandable to developers.
Before Docker, virtual machine technology already supported incremental backups, but the VM-level deltas are defined in the binary byte space, where even a minor business change may lead to massive byte-level changes—highly unstable and devoid of business semantics, rarely of standalone value. Docker, by contrast, defines delta merge rules in the file system space. Docker images have clear business semantics, can be dynamically constructed via the DockerFile DSL, and can be uploaded to a central registry for storage and retrieval—thus opening a complete technical route to application construction based on the delta concept.
Reversible Computation theory posits that behind various delta-based technical practices lies a unified principle of software construction, expressible as:
App = Delta x-extends Generator
For a detailed introduction to Reversible Computation, see Reversible Computation: Next-Generation Software Construction Theory.
The Nop platform is a reference implementation of Reversible Computation. With Nop’s Delta customization mechanism, at zero extra cost we can achieve fully incremental customized software development. The next section details how this works in Nop.
III. Delta Customization in the Nop Platform
All applications developed with the Nop platform are automatically delta-customizable. Using an e-commerce application as an example, we demonstrate how to add, modify, and delete functionality across various layers of the system without changing base product source code. See the sample code in nop-app-mall/app-mall-delta.
3.1 Dedicated Delta Module
All delta customization code can be stored in a dedicated module, such as app-mall-delta.
In the app-mall-codegen module, add the following calls to gen-orm.xgen, indicating that delta customization code will be generated under the app-mall-delta module:
codeGenerator.withTargetDir("../app-mall-delta").renderModel('../../model/nop-auth-delta.orm.xlsx','/nop/templates/orm-delta', '/',$scope);
codeGenerator.withTargetDir("../app-mall-delta").renderModel('../../model/nop-auth-delta.orm.xlsx','/nop/templates/meta-delta', '/',$scope);
In other modules, such as app-mall-app, simply depend on the app-mall-delta module to customize built-in Nop platform features.
3.2 Delta Customization of Data Models
The nop-auth module is the Nop platform’s default access control module. Nop automatically generates ORM model definitions and GraphQL type definitions based on the data model nop-auth.orm.xlsx. If we need to add fields to the system’s built-in user table, we can add a delta model nop-auth-delta.orm.xlsx containing only the tables and fields to be extended.

We added a MALL_USER_ID field to the NopAuthUser table, linking to the LitemallUser table defined in the nop-app-mall project.
If the delta model references entity classes defined in other modules, you must use the full entity name, such as app.mall.dao.entity.LitemallUser.

Since no code needs to be generated for these external tables, add the not-gen tag to the [Tags] of the LitemallUser table, and retain only the primary key definition for the table’s fields to satisfy model integrity checks.

Note: appName must match the name of your customized module; otherwise customization will fail and runtime errors about duplicate entity definitions will occur.
In the data model configuration, set deltaDir=default, so generated model files go to /_vfs/_delta/{deltaDir}/{originalPath}. During model loading, files under the delta directory are loaded first to override base product definitions.
The actual generated ORM model structure is:
x:extends="super,default/nop-auth.orm.xml">
className="app.mall.delta.dao.entity.NopAuthUserEx " displayName="用户"
name="io.nop.auth.dao.entity.NopAuthUser">
...[/LIST]
More...
For concrete customization examples, see the sample project nop-app-mall/app-mall-delta. For the theory of Reversible Computation, see Reversible Computation: Next-Generation Software Construction Theory.
I. The Predicament of Customized Development
Traditionally, we mainly adopt two technical approaches to address customized development:
1.1 Code Branching
The most common approach is to create a dedicated code branch for each customer and then periodically merge from the trunk. Based on our observations, once there are more than five branches, confusion arises easily—especially when the same team maintains multiple branches with major differences simultaneously—leading even to erroneous commits and releasing the wrong versions.
Base products are generally complex and have numerous dependencies. Each branch containing similar yet not identical copies of the base product’s code causes development environment maintenance costs to skyrocket. When issues arise and diagnosis is needed, it’s often hard to quickly determine whether the root cause lies in the modified base product code or in the newly developed customization code because the base product code has been frequently altered.
The base product typically has a large codebase, and the effort and expertise required to perform diff analysis during code synchronization are both significant. If a bug is fixed or new functionality is added in the base product, synchronizing these changes downstream often becomes a prolonged process that must be carried out by developers who clearly understand the reasons behind the changes. Conversely, when an excellent feature in a customization branch is to be reverse-extracted and merged back into the main trunk, peeling off general-purpose code from that customization branch is also quite complex. Base product code and bespoke customization code get entangled and intermingled, lacking clear formal boundaries, easily forming spaghetti dependencies that are hard to disentangle.
1.2. Configurability and Pluggability
Configurability and pluggability constitute the other major technical route for supporting customized development. A mature productized offering must be highly configurable, with a large number of customer demands abstracted into cross-combinations of configuration items. For demand variations that are hard to exhaustively enumerate upfront, if we can anticipate where changes will occur, we can reserve extension points (extension interfaces) in the base product and then inject special plugin implementations during customization.
The main problem with this approach is that predictions can be inaccurate. Especially when the product itself is immature, it’s possible that none of the anticipated variations occur, and changes happen in unanticipated places. This leads to an awkward situation: when we need extensibility the most to reduce product evolution costs, it may not even exist.
High flexibility typically comes with increased complexity and performance overhead at runtime. Some uncommon requirements may leave deep and disproportionate scars inside the base product, leaving later developers puzzled: why is there such a convoluted design here? The requirement is only one sentence—how does it translate into so many interfaces and implementation classes? If we consider specific business requirements as a logical path, then configurability amounts to embedding multiple logical paths into the product to form a crisscrossed network, controlled by numerous switches to enable specific path connections. Without global guiding principles and design planning, configuration itself easily becomes a new source of complexity—hard to understand and hard to reuse.
Based on existing software engineering theory, such as Software Product Line engineering, technical means to enhance software flexibility can be categorized into adaptation, replacement, and extension. They can all be seen as additions to the core architecture. However, customization is not always about adding new functionality; oftentimes it involves hiding or simplifying existing functionality. Current techniques struggle to efficiently achieve the goal of removing existing features.
II. Reversible Computation Theory
Upfront predictions are unreliable; ex-post separation is costly. If we want lightweight customized development, then ideally the customization code and the base product code should be physically separated and, without reserved interfaces, a general mechanism should enable pruning and extension of base product functionality. To achieve this goal, we need to revisit the theoretical foundations of customized development.
Assume we have built a base product X with multiple components, expressed as:
X = A + B + C
We aim to develop a target product Y, which also has multiple components:
Y = A + B + D
Developing product Y based on product X, at the abstract level, corresponds to establishing an operation from X to Y:
Y = A + B + D = (A + B + C) + (-C + D) = X + Delta
If we truly can avoid modifying the base product X, a natural theoretical conclusion is: customization code amounts to a Delta correction applied to the base product.
Furthermore, we can derive the following:
- Delta should be a first-class concept in architectural design—so it can be independently identified, managed, and stored. Customization code should be physically separated from the base product code and versioned independently.
- X = 0 + X. Any quantity applied to the identity element yields itself; therefore a full set is a special case of Delta. Delta’s definition and form need no special design at a theoretical level: any existing formal expression can be seen as a delta expression as long as we define the rules for delta operations.
- Y = X + Delta1 + Delta2 = X + (Delta1 + Delta2) = X + Delta. Delta should satisfy associativity, allowing multiple Deltas to be merged independently of the base product—packaging multiple deltas into one.
- Delta = -C + D. Besides new components, Delta must include inverses to enable pruning of the original system. Delta should be a mixture of additions, modifications, and deletions.
- Y = (A + dA) + (B + dB) + (C + dC) = A + B + C + (dA + dB + dC) = X + Delta. If changes can occur anywhere in the original system, the Delta mechanism must collect changes across the system’s fine-grained parts and aggregate them into an overall Delta. This implicitly requires the original system to have a stable coordinate system. After dA separates from A to be stored in an independent Delta, it must retain some locating coordinates. Only then can the Delta, when combined with X, find the original structure A and integrate with it.
Some highly flexible SaaS products store form configurations and workflow configurations in database tables and achieve customized development per specific users by adjusting configurations. In this approach, the configuration tables and the primary keys of configuration items essentially form a coordinate system. Based on this, one can add version fields to configuration items to enable version management and even version inheritance.
Hot patch mechanisms offered by some software are essentially a Delta correction mechanism. Successful patch application relies on coordinate system positioning provided at the infrastructure level and a delta merge algorithm executed after locating. However, compared to customized development, hot updates impose lower structural requirements on patches: patches need not have relatively stable business semantics, and they may not correspond to source code directly understandable to developers.
Before Docker, virtual machine technology already supported incremental backups, but the VM-level deltas are defined in the binary byte space, where even a minor business change may lead to massive byte-level changes—highly unstable and devoid of business semantics, rarely of standalone value. Docker, by contrast, defines delta merge rules in the file system space. Docker images have clear business semantics, can be dynamically constructed via the DockerFile DSL, and can be uploaded to a central registry for storage and retrieval—thus opening a complete technical route to application construction based on the delta concept.
Reversible Computation theory posits that behind various delta-based technical practices lies a unified principle of software construction, expressible as:
App = Delta x-extends Generator
For a detailed introduction to Reversible Computation, see Reversible Computation: Next-Generation Software Construction Theory.
The Nop platform is a reference implementation of Reversible Computation. With Nop’s Delta customization mechanism, at zero extra cost we can achieve fully incremental customized software development. The next section details how this works in Nop.
III. Delta Customization in the Nop Platform
All applications developed with the Nop platform are automatically delta-customizable. Using an e-commerce application as an example, we demonstrate how to add, modify, and delete functionality across various layers of the system without changing base product source code. See the sample code in nop-app-mall/app-mall-delta.
3.1 Dedicated Delta Module
All delta customization code can be stored in a dedicated module, such as app-mall-delta.
In the app-mall-codegen module, add the following calls to gen-orm.xgen, indicating that delta customization code will be generated under the app-mall-delta module:
codeGenerator.withTargetDir("../app-mall-delta").renderModel('../../model/nop-auth-delta.orm.xlsx','/nop/templates/orm-delta', '/',$scope);
codeGenerator.withTargetDir("../app-mall-delta").renderModel('../../model/nop-auth-delta.orm.xlsx','/nop/templates/meta-delta', '/',$scope);
In other modules, such as app-mall-app, simply depend on the app-mall-delta module to customize built-in Nop platform features.
3.2 Delta Customization of Data Models
The nop-auth module is the Nop platform’s default access control module. Nop automatically generates ORM model definitions and GraphQL type definitions based on the data model nop-auth.orm.xlsx. If we need to add fields to the system’s built-in user table, we can add a delta model nop-auth-delta.orm.xlsx containing only the tables and fields to be extended.

We added a MALL_USER_ID field to the NopAuthUser table, linking to the LitemallUser table defined in the nop-app-mall project.
- The [Index] column for MALL_USER_ID must specify a unique identifier. Typically, choose values starting from “max index in the base model + 50” to avoid conflicts with newly added fields in the base model.
- To ensure structural integrity in the model, we must include the primary key definition in the NopAuthUser table. To avoid duplicate code generation, add the not-gen tag in the [Tags] column, indicating that this field is defined in the base class and corresponding property definition code need not be generated.
- Set the table’s [Object Name] to io.nop.auth.dao.entity.NopAuthUser to preserve the entity name defined in the base model so that existing code remains unaffected.
- Set the table’s [Base Class] to io.nop.auth.dao.entity.NopAuthUser and the [Class Name] to NopAuthUserEx, so the generated entity class inherits from NopAuthUser.
If the delta model references entity classes defined in other modules, you must use the full entity name, such as app.mall.dao.entity.LitemallUser.

Since no code needs to be generated for these external tables, add the not-gen tag to the [Tags] of the LitemallUser table, and retain only the primary key definition for the table’s fields to satisfy model integrity checks.

Note: appName must match the name of your customized module; otherwise customization will fail and runtime errors about duplicate entity definitions will occur.
In the data model configuration, set deltaDir=default, so generated model files go to /_vfs/_delta/{deltaDir}/{originalPath}. During model loading, files under the delta directory are loaded first to override base product definitions.
The actual generated ORM model structure is:
x:extends="super,default/nop-auth.orm.xml">
className="app.mall.delta.dao.entity.NopAuthUserEx " displayName="用户"
name="io.nop.auth.dao.entity.NopAuthUser">
...[/LIST]
More...