| name | architecture-review |
| description | Use after implementing features to verify architecture compliance. Checks for layer violations, missing interfaces, and tight coupling. |
Architecture Review
Overview
Verify that implemented code follows the three-layer architecture with proper dependency injection. This skill catches violations before they become entrenched.
Run this after every feature implementation. Catching violations early prevents technical debt accumulation.
When to Use
Use this skill when:
- Finished implementing a new feature
- Before creating a pull request
- After refactoring code
- Periodically to audit existing code
- When reviewing someone else's code
Architecture Rules
The Three Laws
- Domain layer (
lib/domain/) MUST NOT import from lib/infra/ or lib/db/
- Services MUST receive dependencies via constructor injection
- UI layer MUST only import from
lib/container.ts, never directly from infra
Layer Structure
app/ → Can import: lib/container.ts, lib/domain/**/types
lib/domain/ → Can import: lib/infra/interfaces/* (types only)
lib/infra/interfaces/ → Can import: lib/domain/**/entity types
lib/infra/db/ → Can import: lib/infra/interfaces/*, lib/domain/**/types
lib/infra/storage/ → Can import: lib/infra/interfaces/*
lib/container.ts → Can import: Everything (wires it all together)
Review Checklist
1. Layer Violation Check
Run these commands to detect violations:
grep -r "from.*infra" lib/domain/ 2>/dev/null
grep -r "from.*lib/db" lib/domain/ 2>/dev/null
grep -r "from.*\/db" lib/domain/ 2>/dev/null
grep -r "drizzle-orm" lib/domain/ 2>/dev/null
grep -r "@vercel/blob" lib/domain/ 2>/dev/null
grep -r "from.*lib/infra" app/ 2>/dev/null
grep -r "from.*infra/db" app/ 2>/dev/null
grep -r "from.*lib/db" app/ 2>/dev/null
Expected result: No matches. Any match is a violation that must be fixed.
2. Dependency Injection Check
Verify services use constructor injection:
find lib/domain -name "*.service.ts" -type f
Check each service for:
export class SiteService {
constructor(
private repo: ISiteRepository,
private storage: IStorageAdapter,
) {}
}
export class SiteService {
private repo = new DrizzleSiteRepository();
}
import { DrizzleSiteRepository } from '../infra/db/site.repository';
3. Entity Purity Check
Verify entity files have no side effects:
find lib/domain -name "*.entity.ts" -type f
grep -l "async" lib/domain/**/*.entity.ts 2>/dev/null
grep -l "await" lib/domain/**/*.entity.ts 2>/dev/null
grep -l "fetch" lib/domain/**/*.entity.ts 2>/dev/null
grep -l "import.*db" lib/domain/**/*.entity.ts 2>/dev/null
Entity files should contain ONLY:
- Type/interface definitions
- Pure validation functions (no async, no I/O)
- Constants
- Type guards
4. Interface Completeness Check
Verify interfaces exist for all external dependencies:
ls lib/infra/interfaces/
For each repository/adapter:
5. Container Wiring Check
Verify container properly wires dependencies:
cat lib/container.ts
Verify:
6. Import Path Check
Verify UI layer imports correctly:
grep -rh "from.*@/lib" app/ | sort | uniq
Common Violations
Violation 1: Direct Database Import
import { db } from '../../db';
import { sites } from '../../db/schema';
import type { ISiteRepository } from '../../infra/interfaces/site.repository';
constructor(private repo: ISiteRepository) {}
Violation 2: Async Validation
export async function isSubdomainAvailable(subdomain: string): Promise<boolean> {
const existing = await db.select()...
}
export function validateSubdomainFormat(subdomain: string): ValidationResult { }
async createSite(input: CreateSiteInput) {
const formatResult = validateSubdomainFormat(input.subdomain);
if (!formatResult.valid) throw new SiteError(...);
const existing = await this.repo.findBySubdomain(input.subdomain);
if (existing) throw new SiteError('SUBDOMAIN_TAKEN', ...);
}
Violation 3: Service Instantiating Dependencies
export class SiteService {
private repo = new DrizzleSiteRepository();
private storage = new VercelBlobAdapter();
}
export class SiteService {
constructor(
private repo: ISiteRepository,
private storage: IStorageAdapter,
) {}
}
Violation 4: UI Importing Infra
import { createSite } from '@/lib/infra/db/site.repository';
import { getSiteService } from '@/lib/container';
const service = getSiteService();
await service.createSite(...);
Violation 5: Missing Interface
import { DrizzleSiteRepository } from '../../infra/db/site.repository';
export class SiteService {
constructor(private repo: DrizzleSiteRepository) {}
}
import type { ISiteRepository } from '../../infra/interfaces/site.repository';
export class SiteService {
constructor(private repo: ISiteRepository) {}
}
Review Output Template
After running the review, document findings:
## Architecture Review - [Feature/Date]
### Layer Violations
- [ ] Domain → Infra imports: None / [List violations]
- [ ] Domain → DB imports: None / [List violations]
- [ ] UI → Infra imports: None / [List violations]
### Dependency Injection
- [ ] All services use constructor injection: Yes / [List violations]
- [ ] No direct instantiation in services: Yes / [List violations]
### Entity Purity
- [ ] No async in entity files: Yes / [List violations]
- [ ] No I/O in entity files: Yes / [List violations]
### Interface Coverage
- [ ] All repositories have interfaces: Yes / [List missing]
- [ ] All adapters have interfaces: Yes / [List missing]
### Container
- [ ] All services wired in container: Yes / [List missing]
- [ ] Test factories available: Yes / No
### Actions Required
1. [Action item 1]
2. [Action item 2]
Automated Review Script
Create this script for quick reviews:
#!/bin/bash
echo "=== Architecture Review ==="
echo ""
echo "1. Checking domain → infra imports..."
DOMAIN_INFRA=$(grep -r "from.*infra" lib/domain/ 2>/dev/null | grep -v ".test.ts")
if [ -n "$DOMAIN_INFRA" ]; then
echo "❌ VIOLATION: Domain imports from infra"
echo "$DOMAIN_INFRA"
else
echo "✅ No domain → infra imports"
fi
echo ""
echo "2. Checking domain → db imports..."
DOMAIN_DB=$(grep -r "from.*\/db" lib/domain/ 2>/dev/null | grep -v ".test.ts")
if [ -n "$DOMAIN_DB" ]; then
echo "❌ VIOLATION: Domain imports from db"
echo "$DOMAIN_DB"
else
echo "✅ No domain → db imports"
fi
echo ""
echo "3. Checking UI → infra imports..."
UI_INFRA=$(grep -r "from.*lib/infra" app/ 2>/dev/null)
if [ -n "$UI_INFRA" ]; then
echo "❌ VIOLATION: UI imports from infra"
echo "$UI_INFRA"
else
echo "✅ No UI → infra imports"
fi
echo ""
echo "4. Checking for async in entity files..."
ASYNC_ENTITY=$(grep -l "async\|await" lib/domain/**/*.entity.ts 2>/dev/null)
if [ -n "$ASYNC_ENTITY" ]; then
echo "❌ VIOLATION: Async found in entity files"
echo "$ASYNC_ENTITY"
else
echo "✅ No async in entity files"
fi
echo ""
echo "5. Checking for direct instantiation in services..."
DIRECT_NEW=$(grep -r "new.*Repository\|new.*Adapter" lib/domain/ 2>/dev/null | grep -v ".test.ts")
if [ -n "$DIRECT_NEW" ]; then
echo "❌ VIOLATION: Direct instantiation in services"
echo "$DIRECT_NEW"
else
echo "✅ No direct instantiation in services"
fi
echo ""
echo "=== Review Complete ==="
Why This Matters
Without architecture reviews:
- Violations accumulate over time
- "Just this once" becomes permanent
- Testing becomes increasingly difficult
- Refactoring becomes risky
With regular reviews:
- Catch violations early when easy to fix
- Maintain clean architecture over time
- Keep codebase testable
- Enable safe refactoring