You can react to changes in model associations by enabling the link-operation cache hook and implementing the appropriate link catcher in an MDriven Framework application.
Association side effects are code that runs when a relationship between domain objects changes. For example, when an Invoice is linked to a Customer, you may need to update related state or perform a check.
Use this technique only when the behavior cannot be expressed declaratively. Prefer model rules, methods, and actions where they describe the requirement better. Side effects make one change cause another change, which can make behavior harder to follow and can add unnecessary work.
When to use association side effects
Use an association side effect when you must react consistently regardless of which end of an association was changed.
For example, consider a relationship between Invoice and Customer:
- Code may assign the customer from the invoice side.
- Code may add the invoice from the customer side.
- Code may remove or replace the link.
The association represents the same relationship in every case. An association catcher is designed to observe the link operation rather than relying on one particular navigation path.
Do not use an association catcher as the default solution for calculations or UI behavior. If a user explicitly requests work, model that work as an action. If the work must happen on a schedule, use the appropriate periodic action or server-side action instead.
Why associations use catchers
Attributes and associations have different interception mechanisms.
| Model member | Intended interception approach | Example |
|---|---|---|
| Attribute | Set the attribute's HasUserCode property when you need generated partial-method interception points.
|
React when Invoice.DueDate is assigned.
|
| Association | Enable the link-operation cache hook and use link catcher interfaces. | React when an Invoice becomes linked to, unlinked from, or reassigned to a Customer.
|
Do not rely on HasUserCode for association changes. Earlier ECO versions exposed that approach for associations, but it could not reliably cover every way a relationship can change because an association has two ends. Use the link-operation mechanism for association side effects instead.
For attribute interception details, see What about HasUserCode in Enterprise Core Objects â MDriven Framework.
Enable link-operation catching
Before an association catcher can receive link operations, enable the cache hook early in your EcoSpace setup.
- Locate the code that initializes your EcoSpace.
- Add a
LinkOperationCacheusing the front-side policy. - Set
Enabledtotrue. - Ensure this initialization runs before the application performs the association changes you want to catch.
new LinkOperationCache(FrontsidePolicy) { Enabled = true };
This opt-in is deliberate. The object cache manages every state change for domain objects. Link-operation catching therefore belongs in application setup only when you have a real association-side-effect requirement.
Implement the appropriate catcher
MDriven Framework provides two interfaces for receiving association link operations:
| Interface | Use it for | Example |
|---|---|---|
ISingleLinkCatcher
|
A single-valued association end. | An Invoice has one current Customer; replacing the customer is a single-link change.
|
IMultiLinkCatcher
|
A multi-valued association end. | A Customer has many Invoices; adding or removing an invoice is a multi-link change.
|
Implement the catcher that matches the association end your code needs to observe. Keep the catcher focused on the affected relationship and make the operation safe for every supported change path.
For the Invoice/Customer example:
- Use
ISingleLinkCatcherwhen your logic belongs to the singleInvoice.Customerend. - Use
IMultiLinkCatcherwhen your logic belongs to the multi-valuedCustomer.Invoicesend.
The catcher is notified when the relationship changes even if code made the change from the opposite end. This is the reason to use the cache-based mechanism rather than adding custom behavior to one generated association accessor.
Design side effects carefully
A catcher participates in the handling of domain-object state changes. Treat it as infrastructure-level code.
- Keep the work small. The cache sees all domain-object state changes, so expensive work in a catcher can affect application performance.
- Change only what the requirement needs. Avoid broad recalculation or scanning unrelated objects for every link operation.
- Avoid change loops. If a catcher changes links or attributes that cause the same logic to run again, make the behavior converge rather than repeatedly triggering itself.
- Put explicit user operations in actions. For example, âRebuild invoice summaryâ is clearer as an action than as an automatic response to every navigation or edit.
- Keep query logic free of intentional side effects. A method marked
IsQuerypromises that it has no intentional side effects and can be used in OCL; see Methods.
Choose a declarative alternative when possible
Before adding a catcher, check whether the behavior is better modeled at the point where the user or system requests it.
| Requirement | Prefer | Example |
|---|---|---|
| Run work when a user chooses a command | Action | A user selects âCreate follow-upâ. |
| Reuse a calculation without changing state | Query method | Determine whether an invoice meets a rule. |
| Perform background or single-performer work | Server-side action | Delegate remote-system updates or email work to MDrivenServer. |
| React to any change of a particular association, from either end | Link-operation catcher | Maintain required related state when an invoice/customer link changes. |
Derived settable members can also be appropriate when the assignment itself represents an explicit operation. For example, assigning a derived settable association can create related transient objects at the moment the assignment occurs. Use this approach when the side effect is part of the modeled assignment, not a hidden reaction elsewhere.
Troubleshooting
If association side effects do not run as expected, check the following:
- Confirm that
new LinkOperationCache(FrontsidePolicy) { Enabled = true }runs during EcoSpace initialization. - Confirm that the catcher matches the association end:
ISingleLinkCatcherfor a single-valued end andIMultiLinkCatcherfor a multi-valued end. - Test changes from both association ends. For example, assign
Invoice.Customerand also modifyCustomer.Invoices. - Check that the catcher does not throw an exception or create a repeated chain of state changes.
- Profile or inspect the affected workflow if performance degrades. The cache processes all domain-object state changes, so unnecessary work is multiplied across normal application activity.
