Architectures and Connections
An architecture composes interfaces and modules into a running system. It defines how events flow between components by establishing connections, typed channels with well-defined dispatch semantics.
Declaring an Architecture
Use the @architecture decorator to declare the top-level composition. Inside the
class, list the interfaces and wire them together with connections.
1from pyrapide import architecture, connect, pipe, agent, Pattern
2
3@architecture
4class MySystem:
5 api = APIService
6 db = DatabaseService
7 cache = CacheService
8 notifications = NotificationService
9
10 connections = [
11 connect(Pattern.match("APIService.request"), target=db),
12 connect(Pattern.match("APIService.request"), target=cache),
13 pipe(Pattern.match("DatabaseService.result"), target=api),
14 agent(Pattern.match("DatabaseService.error"), target=notifications),
15 ]
Each attribute on the architecture class is an interface. The connections list
declares how events emitted by one interface are routed to another.
Connection Types
PyRapide offers three connection types, each with different dispatch semantics suited to different use cases:
| Connection | Factory | Semantics |
|---|---|---|
| Basic | connect(pattern, target) | Synchronous dispatch. The emitting module blocks until the target processes the event. Use for request-response flows where ordering matters. |
| Pipe | pipe(pattern, target) | FIFO queue. Events are buffered and delivered in order, but the emitter does not block. Use for ordered but decoupled processing. |
| Agent | agent(pattern, target) | Independent async dispatch. Events are delivered concurrently with no ordering guarantees. Use for fire-and-forget notifications and parallel processing. |
Basic Connections
A connect() is the simplest connection type. When an event matching the pattern is
emitted, it is dispatched synchronously to the target. The emitter waits for the handler to
complete before continuing.
# Synchronous: API waits for the database to finish before responding
connect(Pattern.match("APIService.request"), target=db)
Pipe Connections
A pipe() introduces a FIFO buffer between the source and target. Events are
guaranteed to arrive in order, but the emitter does not block. This is ideal for workflows where
you need ordering guarantees without tight coupling.
# FIFO: results are queued and delivered in order
pipe(Pattern.match("DatabaseService.result"), target=api)
Agent Connections
An agent() connection dispatches events independently and asynchronously. There are
no ordering guarantees; events may be processed concurrently by the target. This is the most
performant option for side effects that do not need coordination.
# Async: error notifications are dispatched independently
agent(Pattern.match("DatabaseService.error"), target=notifications)
Pattern-Based Routing
All three connection types accept a Pattern as their first argument. This allows fine-grained routing. You can connect different event subsets from the same source to different targets:
connections = [
# Route queries to the database
connect(Pattern.match("APIService.request")
.where(lambda e: e.payload.get("type") == "query"),
target=db),
# Route cache lookups to the cache
connect(Pattern.match("APIService.request")
.where(lambda e: e.payload.get("type") == "cache"),
target=cache),
]
Running an Architecture
Once declared, bind modules to interfaces and run the architecture:
import asyncio
system = MySystem.bind(
api=FastAPIModule,
db=PostgresModule,
cache=RedisModule,
notifications=SlackModule,
)
asyncio.run(system.run())
Connections rely on Patterns for event matching. Learn the full pattern algebra next.