From grimoire
Wraps unstable design points (third-party APIs, external systems, volatile algorithms) behind stable interfaces to prevent change ripple effects. Implements GRASP Protected Variations principle.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-protected-variationsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Identify the points of instability in a design; wrap them in a stable interface so that variations at those points do not ripple through the system.
Identify the points of instability in a design; wrap them in a stable interface so that variations at those points do not ripple through the system.
Adopted by: GRASP (Larman, 2004); the most general expression of a principle behind SOLID's Open/Closed Principle (closed to modification via stable interface), Dependency Inversion (depend on abstractions), and encapsulation (hide implementation behind a stable API). Protected Variations is the why behind these patterns — they exist because instability at one point must not force change everywhere. Impact: Alistair Cockburn's Hexagonal Architecture (2005) and Robert Martin's Clean Architecture (2017) are architectural applications of Protected Variations at scale — the stable domain is wrapped in adapter interfaces that protect it from unstable external systems (UI, database, third-party APIs). The Strangler Fig pattern (for legacy system replacement) is Protected Variations in time: wrap the unstable legacy code behind a stable interface before replacing it. Why best: Every system has two kinds of code: stable (domain rules, core logic) and volatile (third-party APIs, external formats, infrastructure providers). Without Protected Variations, volatile code is directly imported into stable code — every change to the volatile part forces changes in the stable part. Protected Variations draws a line and builds a stable interface at that line. This is the principle that makes software tolerant of change.
Sources: Larman, "Applying UML and Patterns" (Prentice Hall, 2004); Cockburn, "Hexagonal Architecture" (2005); Martin, "Clean Architecture" (Prentice Hall, 2017)
Before designing, explicitly enumerate what is likely to change. Sources of instability:
Mark these in design documents. Each is a candidate for a protective interface.
The stable interface expresses what the rest of the system needs, not what the volatile implementation provides. Design the interface from the consumer's perspective.
# Volatile: payment providers change (Stripe → Adyen, or adding PayPal)
# Stable interface: what our system needs from any payment provider
class PaymentGateway(ABC): # stable — won't change when provider changes
@abstractmethod
def charge(self, amount: Money, source: PaymentSource) -> ChargeResult: ...
@abstractmethod
def refund(self, charge_id: str, amount: Money) -> RefundResult: ...
# Volatile implementations hide behind the stable interface
class StripeGateway(PaymentGateway): ...
class AdyenGateway(PaymentGateway): ...
class PayPalGateway(PaymentGateway): ...
When the point of instability is algorithm or behavior selection (which algorithm to use, which provider to call), use Polymorphism to protect against variation.
# Volatile: tax calculation rules vary by country, change with tax laws
class TaxCalculator(ABC):
@abstractmethod
def calculate(self, order: Order) -> Money: ...
class USTaxCalculator(TaxCalculator): # US rules
def calculate(self, order): ...
class EUVATCalculator(TaxCalculator): # EU VAT rules
def calculate(self, order): ...
class OrderService:
def __init__(self, tax_calculator: TaxCalculator): # stable — unaffected by tax changes
self.tax_calculator = tax_calculator
When the volatile point is an external API whose format or protocol differs from what the system needs, use an Adapter to translate.
# External shipping API has a messy interface that changes with API versions
class FedExShippingAPI:
def create_shipment_v3(self, shipper_acct, weight_oz, dest_zip, service_type): ...
# Stable interface our system uses
class ShippingProvider(ABC):
@abstractmethod
def ship(self, order: Order, destination: Address) -> TrackingNumber: ...
# Adapter translates — only this class changes when FedEx API changes
class FedExAdapter(ShippingProvider):
def ship(self, order, destination):
return self.fedex_api.create_shipment_v3(
shipper_acct=FEDEX_ACCOUNT,
weight_oz=order.weight_oz,
dest_zip=destination.zip,
service_type="GROUND"
)
At the application architecture level, Protected Variations means the domain (core) is surrounded by ports (stable interfaces) and adapters (volatile implementations). The domain never imports infrastructure.
[Web Handler] → [Port: OrderUseCase] → [Domain: OrderService, Order]
[REST API] ↗ ↘ [Port: OrderRepository]
↘ [MySQL Adapter | InMemory Adapter]
The domain is stable. All variation (web vs. API, MySQL vs. MongoDB, Stripe vs. PayPal) lives outside it, behind ports.
Protecting against everything. Not every class needs an interface. Wrapping stable internal utility classes in interfaces adds indirection without protection. Only protect against genuine variation points.
Stable interface that leaks volatile details. An interface method def charge_stripe_v3(self, card_token: str) is not stable — it leaks Stripe and version details. Stable interfaces use domain vocabulary, not provider vocabulary.
Premature protection. Designing elaborate interfaces for variation that never happens is YAGNI. Apply Protected Variations when there is a known variation point (current or highly probable), not as a general precaution.
datetime, Java java.lang.* — wrapping these in interfaces adds no protection because they don't change in breaking waysnpx claudepluginhub jeffreytse/grimoire --plugin grimoireGuides extracting interfaces behind abstractions to decouple callers from concrete implementations, improving testability and reducing change impact.
Designs and reviews public APIs, exported function signatures, module boundaries, types/interfaces, and code contracts to make correct usage easy and incorrect usage hard.
Guides system architecture decisions: modular design, interface stability, dependency management, and AI-code health. Activates on architecture, design, structure, modules, dependencies, coupling, system design.