Agent Skills Framework Extension (Optional)
System Architecture Design Skill
When to Use This Skill
Use this skill when implementing system architecture design patterns in your codebase.
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
Enterprise system design with microservices decomposition, component boundaries, and scalability patterns.
Core Capabilities
- Microservices Design - Service decomposition, boundaries
- Domain-Driven Design - Bounded contexts, aggregates
- Event-Driven Architecture - Event sourcing, CQRS
- Scalability Patterns - Horizontal scaling, caching
- Architecture Documentation - C4 diagrams, ADRs
C4 Model Architecture
Level 1: System Context
Level 2: Container Diagram
Level 3: Component Diagram
Domain-Driven Design
Bounded Contexts
// src/contexts/project/domain/project.ts
/**
* Project Aggregate Root
* Bounded Context: Project Management
*/
export class Project extends AggregateRoot {
private constructor(
public readonly id: ProjectId,
private name: ProjectName,
private description: Description,
private status: ProjectStatus,
private settings: ProjectSettings,
private members: ProjectMember[],
private createdAt: Date,
private updatedAt: Date
) {
super();
}
static create(props: CreateProjectProps): Project {
const project = new Project(
ProjectId.generate(),
new ProjectName(props.name),
new Description(props.description),
ProjectStatus.ACTIVE,
ProjectSettings.default(),
[ProjectMember.owner(props.ownerId)],
new Date(),
new Date()
);
project.addDomainEvent(new ProjectCreatedEvent(project.id, props.ownerId));
return project;
}
addMember(userId: UserId, role: MemberRole): void {
if (this.members.some(m => m.userId.equals(userId))) {
throw new DomainError('User is already a member');
}
const member = new ProjectMember(userId, role, new Date());
this.members.push(member);
this.updatedAt = new Date();
this.addDomainEvent(new MemberAddedEvent(this.id, userId, role));
}
archive(): void {
if (this.status === ProjectStatus.ARCHIVED) {
throw new DomainError('Project is already archived');
}
this.status = ProjectStatus.ARCHIVED;
this.updatedAt = new Date();
this.addDomainEvent(new ProjectArchivedEvent(this.id));
}
// Invariant: At least one owner must exist
private validateOwnerExists(): void {
const hasOwner = this.members.some(m => m.role === MemberRole.OWNER);
if (!hasOwner) {
throw new DomainInvariantViolation('Project must have at least one owner');
}
}
}
// Value Objects
export class ProjectName {
constructor(private readonly value: string) {
if (!value || value.length < 3 || value.length > 100) {
throw new DomainError('Project name must be between 3 and 100 characters');
}
}
toString(): string {
return this.value;
}
}
export class ProjectId {
private constructor(private readonly value: string) {}
static generate(): ProjectId {
return new ProjectId(crypto.randomUUID());
}
static fromString(id: string): ProjectId {
return new ProjectId(id);
}
equals(other: ProjectId): boolean {
return this.value === other.value;
}
toString(): string {
return this.value;
}
}
Domain Events
// src/contexts/project/domain/events.ts
export interface DomainEvent {
eventId: string;
aggregateId: string;
occurredAt: Date;
eventType: string;
}
export class ProjectCreatedEvent implements DomainEvent {
readonly eventId = crypto.randomUUID();
readonly occurredAt = new Date();
readonly eventType = 'ProjectCreated';
constructor(
readonly aggregateId: string,
readonly ownerId: string
) {}
}
export class MemberAddedEvent implements DomainEvent {
readonly eventId = crypto.randomUUID();
readonly occurredAt = new Date();
readonly eventType = 'MemberAdded';
constructor(
readonly aggregateId: string,
readonly userId: string,
readonly role: string
) {}
}
// Event Handler
export class ProjectEventHandler {
constructor(
private readonly notificationService: NotificationService,
private readonly analyticsService: AnalyticsService
) {}
@EventHandler(ProjectCreatedEvent)
async onProjectCreated(event: ProjectCreatedEvent): Promise<void> {
await this.notificationService.notify(event.ownerId, {
type: 'project_created',
projectId: event.aggregateId,
});
await this.analyticsService.track({
event: 'project_created',
userId: event.ownerId,
properties: { projectId: event.aggregateId },
});
}
}
Event-Driven Architecture
Event Sourcing
// src/infrastructure/event-store.ts
export interface EventStore {
append(streamId: string, events: DomainEvent[], expectedVersion: number): Promise<void>;
read(streamId: string, fromVersion?: number): Promise<DomainEvent[]>;
subscribe(eventTypes: string[], handler: EventHandler): Subscription;
}
export class PostgresEventStore implements EventStore {
constructor(private readonly pool: Pool) {}
async append(
streamId: string,
events: DomainEvent[],
expectedVersion: number
): Promise<void> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
// Optimistic concurrency check
const { rows } = await client.query(
'SELECT MAX(version) as version FROM events WHERE stream_id = $1',
[streamId]
);
const currentVersion = rows[0]?.version ?? -1;
if (currentVersion !== expectedVersion) {
throw new ConcurrencyError(
`Expected version ${expectedVersion}, got ${currentVersion}`
);
}
// Append events
for (let i = 0; i < events.length; i++) {
const event = events[i];
await client.query(
`INSERT INTO events (stream_id, version, event_id, event_type, data, occurred_at)
VALUES ($1, $2, $3, $4, $5, $6)`,
[
streamId,
expectedVersion + i + 1,
event.eventId,
event.eventType,
JSON.stringify(event),
event.occurredAt,
]
);
}
await client.query('COMMIT');
// Publish to message bus
for (const event of events) {
await this.messageBus.publish(event.eventType, event);
}
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
async read(streamId: string, fromVersion: number = 0): Promise<DomainEvent[]> {
const { rows } = await this.pool.query(
`SELECT data FROM events
WHERE stream_id = $1 AND version >= $2
ORDER BY version`,
[streamId, fromVersion]
);
return rows.map(row => row.data as DomainEvent);
}
}
CQRS Pattern
// src/infrastructure/cqrs.ts
// Command Side
export interface Command {
readonly type: string;
}
export interface CommandHandler<T extends Command> {
execute(command: T): Promise<void>;
}
export class CreateProjectCommand implements Command {
readonly type = 'CreateProject';
constructor(
readonly name: string,
readonly description: string,
readonly ownerId: string
) {}
}
export class CreateProjectHandler implements CommandHandler<CreateProjectCommand> {
constructor(
private readonly projectRepository: ProjectRepository,
private readonly eventStore: EventStore
) {}
async execute(command: CreateProjectCommand): Promise<void> {
const project = Project.create({
name: command.name,
description: command.description,
ownerId: command.ownerId,
});
await this.eventStore.append(
project.id.toString(),
project.domainEvents,
-1
);
await this.projectRepository.save(project);
}
}
// Query Side
export interface Query<TResult> {
readonly type: string;
}
export interface QueryHandler<TQuery extends Query<TResult>, TResult> {
execute(query: TQuery): Promise<TResult>;
}
export class GetProjectQuery implements Query<ProjectDTO> {
readonly type = 'GetProject';
constructor(readonly projectId: string) {}
}
export class GetProjectHandler implements QueryHandler<GetProjectQuery, ProjectDTO> {
constructor(private readonly readDb: Pool) {}
async execute(query: GetProjectQuery): Promise<ProjectDTO> {
const { rows } = await this.readDb.query(
`SELECT * FROM project_view WHERE id = $1`,
[query.projectId]
);
if (rows.length === 0) {
throw new NotFoundError('Project not found');
}
return rows[0] as ProjectDTO;
}
}
Architecture Decision Records
# ADR-001: Microservices Architecture
## Status
Accepted
## Context
We need to design a scalable, maintainable architecture for the CODITECT platform that supports:
- Multi-tenant isolation
- Independent deployment of features
- Team autonomy
- High availability
## Decision
Adopt microservices architecture with the following services:
- API Gateway (Kong)
- Backend Service (Rust/Actix)
- Worker Service (Python)
- AI Router (Python)
## Consequences
### Positive
- Independent scaling per service
- Technology flexibility per bounded context
- Isolated failure domains
- Parallel team development
### Negative
- Increased operational complexity
- Distributed system challenges (network, consistency)
- Requires robust observability
- Higher initial development overhead
## Alternatives Considered
### Modular Monolith
Rejected due to scaling limitations and deployment coupling.
### Serverless
Rejected due to cold start latency concerns for IDE use case.
---
# ADR-002: Event Sourcing for Audit Trail
## Status
Accepted
## Context
SOC2 compliance requires complete audit trails. Standard CRUD approaches lose historical context.
## Decision
Use event sourcing for project and user domains:
- All state changes recorded as immutable events
- Event store as source of truth
- Read models rebuilt from events
## Consequences
### Positive
- Complete audit history
- Time-travel debugging
- Compliance-ready by design
- Event replay for analytics
### Negative
- Learning curve for team
- Eventual consistency complexity
- Storage growth (mitigated by snapshots)
- Complex queries require CQRS
Scalability Patterns
// src/patterns/circuit-breaker.ts
export class CircuitBreaker<T> {
private state: 'closed' | 'open' | 'half-open' = 'closed';
private failures = 0;
private lastFailure?: Date;
private successCount = 0;
constructor(
private readonly options: {
failureThreshold: number;
resetTimeout: number;
halfOpenRequests: number;
}
) {}
async execute(operation: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (this.shouldReset()) {
this.state = 'half-open';
this.successCount = 0;
} else {
throw new CircuitOpenError('Circuit breaker is open');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void {
if (this.state === 'half-open') {
this.successCount++;
if (this.successCount >= this.options.halfOpenRequests) {
this.state = 'closed';
this.failures = 0;
}
} else {
this.failures = 0;
}
}
private onFailure(): void {
this.failures++;
this.lastFailure = new Date();
if (this.failures >= this.options.failureThreshold) {
this.state = 'open';
}
}
private shouldReset(): boolean {
if (!this.lastFailure) return true;
const elapsed = Date.now() - this.lastFailure.getTime();
return elapsed >= this.options.resetTimeout;
}
}
Usage Examples
Design Microservices Architecture
Apply system-architecture-design skill to decompose monolith into microservices with clear bounded contexts
Create C4 Diagrams
Apply system-architecture-design skill to create C4 model diagrams for the platform architecture
Implement Event Sourcing
Apply system-architecture-design skill to design event sourcing infrastructure with CQRS for audit compliance
Integration Points
- api-design-patterns - API contracts between services
- database-schema-optimization - Data modeling per bounded context
- infrastructure-as-code - Service deployment infrastructure
- monitoring-observability - Distributed tracing across services
Success Output
When successful, this skill MUST output:
✅ SKILL COMPLETE: system-architecture-design
Completed:
- [x] Architecture style selected: {microservices|modular-monolith|serverless|event-driven}
- [x] Bounded contexts defined: {count}
- [x] C4 diagrams created: {system-context|container|component}
- [x] Domain events identified: {count}
- [x] ADRs documented: {count}
- [x] Scalability patterns applied: {patterns_list}
Outputs:
- Architecture Decision Records: {adr_paths}
- C4 Diagrams: {diagram_paths}
- Bounded Contexts: {context_list}
- Domain Model: {aggregates, value_objects, events}
- Infrastructure Requirements: {databases, message_bus, caching}
- Deployment Strategy: {strategy}
Completion Checklist
Before marking this skill as complete, verify:
- Architecture style chosen with rationale (microservices, monolith, serverless)
- Bounded contexts identified and documented (3+ contexts minimum for microservices)
- C4 model diagrams created (Level 1: System Context, Level 2: Containers, Level 3: Components)
- Domain events defined with event sourcing strategy (if applicable)
- ADRs written for major decisions (architecture style, database choice, messaging)
- Scalability patterns documented (circuit breaker, CQRS, event sourcing, caching)
- Service boundaries clear (no cross-context dependencies, clear API contracts)
- Data consistency strategy defined (strong vs. eventual consistency)
- Infrastructure requirements specified (databases, message bus, caching, storage)
- Deployment strategy outlined (containers, orchestration, CI/CD)
Failure Indicators
This skill has FAILED if:
- ❌ Architecture style not justified (no ADR documenting decision rationale)
- ❌ Bounded contexts overlap or have unclear boundaries
- ❌ C4 diagrams missing or incomplete (e.g., only system context, no containers)
- ❌ Domain events defined but no event store implementation plan
- ❌ ADRs missing consequences section (only describes decision, not impacts)
- ❌ Scalability patterns mentioned but not designed (e.g., "use caching" with no cache strategy)
- ❌ Service dependencies create circular references
- ❌ Data consistency strategy undefined (strong/eventual not specified)
- ❌ Infrastructure requirements vague (e.g., "database" without specifying RDBMS vs. NoSQL)
When NOT to Use
Do NOT use this skill when:
- Simple CRUD application with single bounded context (over-engineering)
- Proof-of-concept or MVP (architecture can emerge later)
- Existing architecture well-established (use refactoring patterns instead)
- Non-technical system (business process design, not software architecture)
- UI/UX design project (use design system patterns instead)
- Infrastructure-only project (use infrastructure-as-code patterns)
- Data pipeline/ETL project (use data engineering patterns instead)
Use alternatives:
- Simple design: CRUD app with MVC pattern
- Iterative architecture: Start with monolith, evolve to microservices
- Refactoring: Strangler fig pattern for legacy system migration
- Domain modeling: If only data model needed without full architecture
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Distributed monolith | Microservices with tight coupling | Define clear bounded contexts, eliminate shared databases |
| Premature microservices | Complexity before domain understanding | Start with modular monolith, extract services later |
| Event sourcing everything | Unnecessary complexity | Use event sourcing only for audit/temporal queries |
| No ADRs | Lost context on why decisions made | Document all significant architecture decisions |
| CQRS without justification | Complexity for simple CRUD | Use CQRS only when read/write patterns differ significantly |
| Ignoring CAP theorem | Unrealistic consistency expectations | Choose between consistency and availability explicitly |
| Missing circuit breakers | Cascading failures in distributed system | Implement circuit breaker for all external calls |
Principles
This skill embodies the following CODITECT principles:
- #2 First Principles - Architecture decisions justified from fundamental requirements
- #3 Keep It Simple - Choose simplest architecture that meets requirements (e.g., monolith over microservices if sufficient)
- #4 Separation of Concerns - Bounded contexts isolate domains, clear service boundaries
- #5 Eliminate Ambiguity - Explicit data consistency strategy, clear service contracts
- #6 Clear, Understandable, Explainable - C4 diagrams make architecture visual, ADRs document reasoning
- #8 No Assumptions - Infrastructure requirements explicitly specified, not assumed
- Trust & Transparency - ADRs expose decision rationale and trade-offs
- Factual Grounding - Architecture patterns based on proven practices (DDD, Event Sourcing, CQRS)
Version: 1.1.0 | Created: 2025-12-22 | Updated: 2026-01-04 | Author: CODITECT Team