Agent Wranglers

SOLID and DRY Recommended

Depend on abstractions at the edges

Business rules depend on small interfaces they own. Databases, payment providers and email services implement those interfaces.

How to do it

Define the interface next to the code that uses it, in its words. Pass the implementation in through the constructor. Then tests can pass a fake, and a provider can change without touching the rules. Never create databases, clients or mailers inside business code.

Example

// Bad: the checkout rule imports the payment vendor directly
import { VendorPay } from 'vendor-pay-sdk';

// Good: checkout owns the interface, the vendor adapter implements it
interface PaymentGateway { charge(amount: Money, token: string): Promise<Receipt>; }
class Checkout { constructor(private readonly payments: PaymentGateway) {} }
# Good in Python: checkout owns the protocol, the vendor adapter implements it
class PaymentGateway(Protocol):
    def charge(self, amount: Money, token: str) -> Receipt: ...

class Checkout:
    def __init__(self, payments: PaymentGateway):
        self.payments = payments
// Good in PHP: the interface lives with the checkout code
interface PaymentGateway { public function charge(Money $amount, string $token): Receipt; }
final class Checkout { public function __construct(private PaymentGateway $payments) {} }

How to check

Business code lives in its own folder, such as domain/. An import rule fails when a file in that folder imports a vendor SDK. Examples are ESLint no-restricted-imports, dependency-cruiser, Deptrac and import-linter.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers architecture playbook: https://agentwranglers.se/playbook/architecture/solid-and-dry/dependency-inversion.