How to Apply IVP
Applying the Independent Variation Principle is an iterative design activity, not a mechanical algorithm. It follows a six-step cycle; you revisit earlier steps as understanding deepens. The depth of analysis should be proportionate to the system’s expected lifetime and how often it changes.
This page first lays out the six steps, then works through them on a complete example, following the principle as stated in the published paper on the Independent Variation Principle [1].
1 The six-step process
- Identify the elements. Choose the grain of artifact the analysis operates on — functions, classes, methods, data structures, modules. Every element is something that can change in response to a change driver.
- Identify the change drivers. Read the external conditions whose activation forces the system to change. This is discovery, not invention. Apply the “who asks for this change?” and “independent clock” tests: a change driver is a source of variation that can change without forcing the others to change.
- Map drivers to elements. Assign each element the set of change drivers that force it to change — build the assignment . Record whether each element is pure (one driver) or composite (several).
- Assess the current modularization. Compare the existing module structure to the Γ-equivalence partition. Name the violations: which modules are impure (IVP-3), which are incomplete (IVP-4).
- Refactor toward compliance. Move elements so the structure converges to the partition. Merge modules that scatter one driver’s elements; split modules that mix different drivers.
- Verify and iterate. Re-check the assignment and the partition as understanding deepens and as the domain changes.
2 A worked example: a notification system
To make the process concrete, consider a notification system of the kind found in any SaaS application. It is small enough to fit one page yet rich enough to exhibit both a Unit Purity and a Unit Completeness violation.
2.1 The starting point
The system sends notifications to users when events occur — account creation, order placed, payment failed, subscription expiring. It supports three delivery channels (email, SMS, push) and enforces business rules such as user preferences and rate limits.
A developer has implemented all of this in a single class, NotificationService, containing these nine methods:
| Element | Responsibility |
|---|---|
: resolveTemplate |
Selects the message template for a given event type |
: renderContent |
Populates the template with event-specific data and policy-driven personalization |
: deliverEmail |
Sends the notification via SMTP |
: deliverSMS |
Sends the notification via SMS gateway API |
: deliverPush |
Sends the notification via push notification service |
: checkPreferences |
Evaluates which channels the user has enabled |
: enforceRateLimit |
Throttles notifications per channel (max per hour per user) |
: logDelivery |
Records each delivery attempt for audit compliance and channel metrics |
: formatForChannel |
Adapts the rendered message to each channel’s constraints (e.g. SMS length, push truncation) |
All nine elements live in one module: . As in a real system, the elements differ in how many change drivers force them to change: some are pure (assigned to one driver), others are composite (assigned to several).
2.2 Step 1: System description
The element inventory is the table above; at this granularity, each method is an element. The system’s purpose : deliver event-triggered notifications to users through their preferred channels, subject to business rules.
2.3 Step 2: Change driver identification
Apply the “who asks for this change?” and “independent clock” tests.
- Message content changes when the marketing team redesigns templates, when the UX team changes wording, or when a new event type is added. None of these affect delivery or channel preferences.
- Delivery infrastructure changes when operations switches SMTP providers, when the SMS gateway publishes a new API version, or when a new channel (e.g. WhatsApp) is added. These do not affect message content or business rules.
- Business policy changes when the product team modifies rate limits, when legal requires changes to audit logging, or when new preference options are introduced. These are independent of formatting and delivery.
Analysis reveals three change drivers:
The “independent clock” test confirms their independence: a template redesign () can happen without changing the SMS gateway () or the rate-limiting rules (), and vice versa.
2.4 Step 3: The Γ assignment
| Element | Type | |
|---|---|---|
: resolveTemplate |
pure | |
: renderContent |
composite | |
: deliverEmail |
pure | |
: deliverSMS |
pure | |
: deliverPush |
pure | |
: checkPreferences |
pure | |
: enforceRateLimit |
composite | |
: logDelivery |
composite | |
: formatForChannel |
composite |
Five elements are pure (); four are composite (). Each composite element is irreducibly so — both of its drivers genuinely force it to change:
-
renderContentchanges when the message content changes and when policy-driven personalization (language, consent) changes. -
enforceRateLimitchanges when the policy sets a new limit and when a channel’s capacity changes. -
logDeliverychanges when audit policy changes and when the channel’s reporting format changes. -
formatForChannelchanges when content changes and when a channel’s constraints change.
None of these can be split into sub-elements with strictly smaller driver sets, so each is an irreducible composite — the realistic norm, not a special case.
2.5 Step 4: Compliance assessment
Evaluate the monolithic modularization :
- Element Admissibility (IVP-1). Every element has a non-empty . ✓
- Change-Driver Assignment (IVP-2). Every element is pure or irreducibly composite. ✓ (the four composite elements are all irreducible.)
- Unit Purity (IVP-3). contains and elements with six distinct driver-assignment sets coexist in one module. Violated.
- Unit Completeness (IVP-4). With only one module, all elements responding to each driver are trivially together. ✓ (trivially)
The diagnosis: is a mixed module. Any activation of a change driver touches the whole module, even though only a subset of its elements actually varies. This is the god-module pattern.
2.6 A naive first attempt
A common first instinct is to decompose by technical layer: one module for content handling, one for transport, one for business logic, one for logging.
| Module | Elements | |
|---|---|---|
By technical category, the modules look clean — each holds the elements that “feel like” they belong to that layer. But the driver assignments tell a different story:
- Unit Purity (IVP-3). Three of the four modules are mixed — they group elements that answer to different drivers. mixes content and policy (because is composite), mixes channel and content (because is composite), and mixes policy and channel (because is composite). Violated.
- Unit Completeness (IVP-4). Elements answering to are scattered across , , and ; elements answering to across , , and . Violated.
The technical-layer decomposition fails on both directives. A change in business policy touches three modules; a change in a channel’s infrastructure touches three modules. This is shotgun surgery spread wide, plus hidden coupling: the composite elements land in whichever layer their technical category suggests, silently wiring together drivers that should be separated.
Why this mistake is common. Decomposing by technical layer (presentation, logic, data) is a deeply ingrained habit. Technical layers sometimes conform to change drivers, but not always — and the more composite elements a system has, the more the two diverge. The test is not “what kind of thing is this element?” but “what causes this element to change?” A composite element belongs with the elements it shares drivers with, not with the layer its name suggests.
2.7 Step 5: The IVP-compliant decomposition
The fix is to group elements by their exact driver-assignment set. Each distinct set forms one equivalence class — one module. The nine elements fall into six classes:
| Module | Elements | |
|---|---|---|
- Element Admissibility (IVP-1). Every element has non-empty . ✓
- Change-Driver Assignment (IVP-2). Every element is pure or irreducibly composite. ✓
- Unit Purity (IVP-3). Each module groups elements with the same driver assignment. ✓
- Unit Completeness (IVP-4). Every element of a given assignment set is in the same module. ✓
IVP-compliant. Each distinct driver-assignment set is one module. The pure modules () each hold a single driver’s elements. The composite modules () each hold one assignment set; because their elements are irreducibly multi-driver, these modules are necessarily impure — they must answer to two drivers because the domain forces it.
The driver space is shown as a symmetric Venn diagram below. The driver sets overlap at 120°, and their partial overlaps generate seven regions, each a distinct change-driver-assignment set — a possible partition class. The single-driver regions hold the pure modules; the composite elements occupy the overlap regions.
2.8 Step 6: Reflecting on the result
The three modularizations differ in how far a change propagates. The measure that matters is not how many modules are touched, but how many elements must be inspected or modified when a change driver activates — and how many of those were touched unnecessarily.
When a driver activates, the elements that must change are fixed by the domain: forces ; forces ; forces . The designs differ in how many elements they actually touch beyond that unavoidable set:
| Driver activation | Elements that must change | Monolithic touches | IVP-compliant touches |
|---|---|---|---|
| (3) | 9 | 3 | |
| (6) | 9 | 6 | |
| (4) | 9 | 4 |
The monolithic design places all nine elements in one module, so every driver activation touches all nine elements, even though only a fraction of them actually vary — up to six unnecessary touches per change. The IVP-compliant design touches exactly the elements that must change and nothing else — zero unnecessary touches, because every module holds only the elements of its driver. This is what IVP’s partition theorem guarantees: the IVP-compliant modularization minimizes change propagation — the scope that must be read and modified when a change driver activates.
The composite elements make the per-driver footprint larger than in a pure system — alone forces six elements across three modules — but this is the domain’s cost, not a defect: each of those elements genuinely must change when the channel changes. IVP cannot reduce the required set; it only ensures that nothing outside it is touched.
2.9 What about dependencies between modules?
The modules in will have structural dependencies between them: the orchestration that calls resolveTemplate, then checkPreferences, then deliverEmail introduces inter-module coupling.
IVP does not eliminate dependencies — it organizes them. Dependencies between modules conforming to different change drivers are expected and manageable. Dependencies within a mixed module are the hidden ones that cause maintenance surprises. The composite modules sit between the pure ones by necessity: bridges content and channel, bridges content and policy, and bridge policy and channel. Each is an explicit bridge the domain requires — better a few named bridges than a hidden tangle inside a monolithic module.
The formal development. The definitions, theorems, and proofs behind this page are published in:
- The Independent Variation Principle — Zenodo, 2026 — doi:10.5281/zenodo.20794332