Extension Points on Business Objects: When to Override Them
This article explores architectural approaches to adding custom logic to business objects in enterprise software. As a .NET developer, you will learn when to inherit functionality and how to properly partition steps like 'DoSave' for customization.
Key points
- . When customizing a business object, determine if the solution fits Declarative rules, Plugins, or Inheritance.
- Save and Delete processes feature 'DoSave' and 'DoDelete' steps that maintain transaction atomicity.
- 'DoBeforeSave' sets default values and computed fields, so place custom validation rules in the correct step.
- If an exception is thrown in DoAfterSave, the data may already be written; side effects must be retryable.
- Opening a method for overriding must be driven by a genuine need, as an extension point is a permanent promise.
- DoAfterSave and DoAfterDelete steps perform no state change, so misuse here is likely to be a bug.
This guide delves into the architectural depths of adding custom behavior to a business object. Before deciding to implement code-level customizations, it is crucial to determine if the solution should be delivered as a Declarative rule, a Plugin, or by Inheriting. A save operation, for example, is split into six protected virtual steps: from running validation to committing the database transaction. This clear separation of concerns helps maintain data integrity and defines clear boundaries for each layer.
The greatest architectural challenge lies in deciding whether your custom logic should execute before or after the base implementation. This single decision profoundly affects the state of the data in your hands. For instance, computed fields and default values are typically set by the base implementation, meaning your custom checks must be positioned appropriately relative to these base steps.
Real-world use
- . Designing layered architecture for custom business rules in a highly complex ERP system.
- Designing a layer that optimizes database operations, balancing data integrity and audit logging.
- Developing services that require real-time external API confirmation (e.g., stock availability) during a save operation, managing it within transaction boundaries.
- Implementing unique validation and notification logic for a multi-tenant SaaS application.
- Creating performance-oriented structures by minimizing lock times in long-running critical operations (Save/Delete).
- Determining the correct positioning of custom rules so they can access system-calculated 'computed field' values.
- Managing lifecycle events (Create, Update, Delete) for a business object within a robust C# framework.
- Partitioning logic for critical operations (Save/Delete) to ensure the system rolls back completely upon failure.
- Placing cascading or downstream actions that must occur only after deletion is finalized into 'DoAfterDelete'.
- Applying these architectural principles to build secure, maintainable, and extensible enterprise-level C# frameworks.
Why it matters
Understanding these architectural patterns enables you to build more predictable, maintainable, and extensible C# codebases in large enterprise applications.