Agnostic Core, Specific Edges

Your core business logic should be infrastructure-agnostic. The "edges" of your application handle specifics.

The Pattern

┌──────────────────────────────────────────────────────────────┐
│                         EDGES (IN)                           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │ HTTP Handler │  │  Pub/Sub     │  │  CLI Command │       │
│  │              │  │  Subscriber  │  │              │       │
│  │ JSON →       │  │ message →    │  │ args →       │       │
│  │ domain       │  │ domain       │  │ domain       │       │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘       │
│         │                 │                 │               │
│         ▼                 ▼                 ▼               │
│  ┌──────────────────────────────────────────────────────┐   │
│  │                     CORE                             │   │
│  │                                                      │   │
│  │              Business Logic                          │   │
│  │         (speaks only domain language)                │   │
│  │                                                      │   │
│  └───────┬──────────────────────────────┬───────────┘   │
│          │                              │               │
│          ▼                              ▼               │
│  ┌──────────────┐              ┌──────────────┐         │
│  │  Database    │              │  API Client  │         │
│  │  Repository  │              │              │         │
│  │              │              │  string →    │         │
│  │  SQL →       │              │  boolean     │         │
│  │  domain      │              │              │         │
│  └──────────────┘              └──────────────┘         │
│                        EDGES (OUT)                       │
└──────────────────────────────────────────────────────────┘

Example: Repository Pattern

Edges handle infrastructure. The service owns business logic — both orchestration and transformation.

# repository.py - EDGE (database-specific)
class UserRepository:
    def find_by_id(self, id):
        row = db.query("SELECT * FROM users WHERE id = %s", [id])
        return self._to_domain(row) if row else None

    def save(self, user):
        db.execute("UPDATE users SET status = %s WHERE id = %s",
            ["A" if user.is_active else "I", user.id])

    def _to_domain(self, row):
        return User(
            id=row.id,
            name=row.full_name,            # DB column name → domain name
            is_active=row.status == "A",    # DB code → boolean
        )

# service.py - CORE (business logic)
class UserService:
    def __init__(self, user_repo):
        self.user_repo = user_repo

    def get_active_user(self, user_id):
        user = self.user_repo.find_by_id(user_id)
        if not user:           raise NotFoundError()
        if not user.is_active: return None
        return user

    def deactivate(self, user_id):
        user = self.get_active_user(user_id)
        if not user: return user
        updated = User(**user, is_active=False)
        self.user_repo.save(updated)
        return updated

# handler.py - EDGE (protocol translation)
@route("POST /users/:id/deactivate")
def deactivate_user(request):
    try:
        user = user_service.deactivate(request.params.id)
        return 200, user
    except NotFoundError:
        return 404

Translate at Boundaries

External data formats should be translated at the edge, not in business code.

# api_client.py - EDGE (incoming)
class ExternalApiClient:
    def get_feature_flags(self):
        response = fetch("/api/flags")
        data = response.json()

        # External API sends strings, we use booleans
        return FeatureFlags(
            dark_mode=data["dark_mode"] == "true",
            beta_features=data["beta"] == "1",
        )

# feature_service.py - CORE
class FeatureService:
    # Never sees strings - only typed booleans
    def is_enabled(self, flags, feature):
        return getattr(flags, feature)

Backing Services as Attached Resources

Databases, message queues, caches, external APIs — treat them all the same way. They're resources your app attaches to via configuration.

┌─────────────┐
│    App      │
└──┬──┬──┬──┬─┘
   │  │  │  │
   ▼  ▼  ▼  ▼
  DB MQ S3 API

  All accessed via URL/credentials in config

The principle: Swap any service by changing config, no code changes.

# Switch from local to managed database
DATABASE_URL=postgres://localhost/myapp          # local
DATABASE_URL=postgres://user:pass@rds.aws/myapp  # production

This is agnostic core applied to infrastructure: your app doesn't know or care whether its database is local, managed, or in another continent.

External Dependencies Are Only Up Half the Time

Build systems assuming that anything you don't control will be unreliable. Failures from external services are the normal path, not edge cases. Design for resilience, not optimism.

Draw Consistency Boundaries

Not everything needs to be immediately consistent with everything else. Identify the smallest groups of things that must change together as a unit, and enforce rules at those boundaries. The tighter you draw the boundary, the less contention you create.

Scope Your Models

A single unified model for everything breaks down at scale. When different parts of a system need different views of the same concept, give each its own model within its own boundary rather than forcing one model to serve all purposes.

Why This Matters