---
title: "Keep side effects at the edges · Agent Wranglers"
url: https://agentwranglers.se/playbook/architecture/coupling/side-effects-at-edges
id: ARC-83
level: Recommended
updated: 2026-10-22
build: a2a6bdfed830
tokens: 945
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Keep side effects at the edges

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Coupling](https://agentwranglers.se/playbook/architecture/coupling.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: ARC-83
- Level: Recommended
- Chapter: [Coupling](https://agentwranglers.se/playbook/architecture/coupling.md)
- Rule: Write business rules as functions that take data and return data or decisions. Do the reading, writing, sending and clock reading in a thin outer function that calls them.
- Check: A dependency rule fails the build when a file in `domain/` imports a database driver, an HTTP client or a mail library. It also fails on clock reads there. Examples are dependency-cruiser and import-linter. The unit tests of `domain/` use no mocks.
- How: A rule that reads the database, the clock and the mail server at once can only be tested with all three faked. Split it in two. The outer function loads what the rule needs and calls the rule. Then it carries out what the rule returns. The rule now tests with plain values, as [Fake the clock and the network in unit tests](https://agentwranglers.se/playbook/testing/unit-tests/unit-deterministic.md) wants. It also runs the same in a job, an API call or a script. These rules form the inner ring of [Point dependencies inward](https://agentwranglers.se/playbook/architecture/structure/layers-point-inward.md).

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### TypeScript example

```ts
// The rule: plain data in, a decision out
export function reminderFor(invoice: Invoice, today: Date): Reminder | undefined {
  const daysLate = differenceInDays(today, invoice.dueDate);
  return daysLate >= 14 && !invoice.paid ? { to: invoice.customerEmail, daysLate } : undefined;
}

// The edge: reads, calls the rule, then acts
export async function sendReminders(db: Db, mailer: Mailer, clock: Clock) {
  for (const invoice of await db.unpaidInvoices()) {
    const reminder = reminderFor(invoice, clock.today());
    if (reminder) await mailer.sendReminder(reminder);
  }
}
```

### Python example

```python
# The rule: plain data in, a decision out
def reminder_for(invoice: Invoice, today: date) -> Reminder | None:
    days_late = (today - invoice.due_date).days
    return Reminder(invoice.customer_email, days_late) if days_late >= 14 and not invoice.paid else None

# The edge: reads, calls the rule, then acts
def send_reminders(db: Db, mailer: Mailer, clock: Clock) -> None:
    for invoice in db.unpaid_invoices():
        if reminder := reminder_for(invoice, clock.today()):
            mailer.send_reminder(reminder)
```

## Related rules

- TST-5 [Fake the clock and the network in unit tests](https://agentwranglers.se/playbook/testing/unit-tests/unit-deterministic.md) in Testing
- ARC-63 [Point dependencies inward](https://agentwranglers.se/playbook/architecture/structure/layers-point-inward.md) in Clean code and architecture

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/architecture/coupling/side-effects-at-edges">Agent Wranglers clean code and architecture playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Keep internals private](https://agentwranglers.se/playbook/architecture/coupling/private-internals.md)
- [Coupling](https://agentwranglers.se/playbook/architecture/coupling.md)
