"Kiến trúc tốt không cần tài liệu 100 trang. Kiến trúc tốt cần automated tests để enforce rules." - Neal Ford
1. Architecture Fitness Functions
Concept
Fitness function là automated test kiểm tra architecture rules - thay vì dựa vào convention và hy vọng dev tuân thủ, ta viết test để bắt violations ngay trong CI/CD.
ArchUnitNET - Architecture Testing cho .NET
bashdotnet add package TngTech.ArchUnitNET.xUnit
Layer Dependency Rules
csharpusing ArchUnitNET.Domain;
using ArchUnitNET.Loader;
using ArchUnitNET.Fluent;
using static ArchUnitNET.Fluent.ArchRuleDefinition;
public class LayerArchitectureTests
{
private static readonly Architecture Architecture =
new ArchLoader()
.LoadAssemblies(
typeof(MyApp.Domain.Order).Assembly,
typeof(MyApp.Application.OrderService).Assembly,
typeof(MyApp.Infrastructure.OrderRepository).Assembly,
typeof(MyApp.Api.OrderController).Assembly)
.Build();
private readonly IObjectProvider<IType> DomainLayer =
Types().That().ResideInNamespace("MyApp.Domain").As("Domain Layer");
private readonly IObjectProvider<IType> ApplicationLayer =
Types().That().ResideInNamespace("MyApp.Application").As("Application Layer");
private readonly IObjectProvider<IType> InfrastructureLayer =
Types().That().ResideInNamespace("MyApp.Infrastructure").As("Infrastructure Layer");
private readonly IObjectProvider<IType> ApiLayer =
Types().That().ResideInNamespace("MyApp.Api").As("API Layer");
[Fact]
public void Domain_Should_Not_Depend_On_Any_Other_Layer()
{
IArchRule rule = Types().That().Are(DomainLayer)
.Should().NotDependOnAny(ApplicationLayer)
.AndShould().NotDependOnAny(InfrastructureLayer)
.AndShould().NotDependOnAny(ApiLayer);
rule.Check(Architecture);
}
[Fact]
public void Application_Should_Not_Depend_On_Infrastructure()
{
IArchRule rule = Types().That().Are(ApplicationLayer)
.Should().NotDependOnAny(InfrastructureLayer)
.AndShould().NotDependOnAny(ApiLayer);
rule.Check(Architecture);
}
[Fact]
public void Controllers_Should_Not_Access_Repositories_Directly()
{
IArchRule rule = Classes().That().HaveNameEndingWith("Controller")
.Should().NotDependOnAny(
Classes().That().HaveNameEndingWith("Repository"));
rule.Check(Architecture);
}
}
Naming Convention Tests
csharp[Fact]
public void Services_Should_Be_In_Application_Layer()
{
IArchRule rule = Classes().That().HaveNameEndingWith("Service")
.Should().ResideInNamespace("MyApp.Application", true);
rule.Check(Architecture);
}
[Fact]
public void Repositories_Should_Implement_Interface()
{
IArchRule rule = Classes().That().HaveNameEndingWith("Repository")
.Should().ImplementInterface(typeof(IRepository<>).FullName!);
rule.Check(Architecture);
}
[Fact]
public void Domain_Entities_Should_Not_Have_Public_Setters()
{
IArchRule rule = Classes().That().Are(DomainLayer)
.And().AreNotAbstract()
.Should().NotHaveAnyFieldsMutableByOtherTypes();
rule.Check(Architecture);
}
Performance Fitness Functions
csharp[Fact]
public async Task Api_Response_Time_Should_Be_Under_200ms()
{
var stopwatch = Stopwatch.StartNew();
var response = await _client.GetAsync("/api/orders?page=1&size=20");
stopwatch.Stop();
response.EnsureSuccessStatusCode();
Assert.True(stopwatch.ElapsedMilliseconds < 200,
$"Response time {stopwatch.ElapsedMilliseconds}ms exceeds 200ms SLA");
}
[Fact]
public void Database_Query_Should_Not_Exceed_N_Plus_One()
{
using var context = CreateDbContext();
var queryCount = 0;
context.Database.Log = _ => queryCount++;
var orders = context.Orders.Include(o => o.Items).ToList();
Assert.True(queryCount <= 2,
$"Expected <= 2 queries but executed {queryCount}. N+1 detected!");
}
2. Architecture Review Board Process
Architecture Review Board (ARB)
| Aspect | Detail |
|---|---|
| Members | CTO/VP Eng, Senior Architects, Tech Leads (rotating) |
| Frequency | Bi-weekly hoặc monthly |
| Duration | 60 phút |
| Input | Architecture Decision Records (ADR), RFC documents |
| Output | Approve / Request Changes / Reject với documented reasoning |
Review Process Flow
Developer/Tech Lead viết RFC/ADR
↓
Async review bởi 2+ ARB members (3 ngày)
↓
ARB Meeting - trình bày và thảo luận
↓
Decision: Approve / Revise / Reject
↓
Document decision + rationale trong ADR
↓
Implement + architecture tests
Architecture Decision Record (ADR) Template
markdown# ADR-015: Chuyển từ Monolith sang Event-Driven Microservices
## Status
Proposed | Accepted | Deprecated | Superseded by ADR-XXX
## Context
Hệ thống hiện tại là monolith .NET 6 phục vụ 50K DAU.
Thời gian deploy trung bình 45 phút, mỗi lần deploy ảnh hưởng
toàn bộ hệ thống. Team scale từ 5 lên 15 devs, merge conflicts
tăng 300% trong 6 tháng qua.
## Decision
Tách monolith thành 4 bounded contexts:
- Order Service (team Order)
- Payment Service (team Payment)
- Inventory Service (team Inventory)
- Notification Service (shared)
Communication qua RabbitMQ (async events) + gRPC (sync queries).
## Consequences
### Positive
- Mỗi team deploy độc lập (deploy time < 5 phút)
- Scale riêng từng service theo load
- Tech stack flexibility per service
### Negative
- Distributed system complexity (eventual consistency, observability)
- Cần đầu tư infrastructure (message broker, service mesh)
- Learning curve cho team (6-8 tuần)
### Risks
- Data consistency giữa services (mitigate: Saga pattern)
- Network latency (mitigate: caching, circuit breaker)
- Operational complexity (mitigate: centralized logging, tracing)
## Alternatives Considered
1. **Modular Monolith** - ít complexity hơn nhưng vẫn single deploy
2. **Strangler Fig** - migrate dần, được chọn làm migration strategy
3. Decision Authority Matrix
Ai quyết định cái gì?
| Decision Type | Ví dụ | Decision Maker | Consulted | Informed |
|---|---|---|---|---|
| Strategic Architecture | Monolith vs Microservices, Cloud provider | CTO + ARB | Tech Leads, PO | All engineering |
| Service Architecture | Database choice per service, API style | Tech Lead + ARB | Senior devs, DevOps | Team members |
| Module Design | Design patterns, class structure | Senior Dev | Team members | Tech Lead |
| Implementation | Algorithm choice, library selection | Developer | Peer (via PR review) | Tech Lead |
| Coding Standards | Naming, formatting, lint rules | Tech Lead + Team consensus | All devs | New joiners |
| Emergency Changes | Production hotfix architecture | On-call Senior + Tech Lead | Engineering Manager | ARB (post-mortem) |
RACI cho Architecture Decisions
R = Responsible (thực hiện)
A = Accountable (chịu trách nhiệm cuối cùng)
C = Consulted (tham vấn)
I = Informed (được thông báo)
Dev Sr.Dev TechLead Architect CTO
New microservice C R A C I
Database migration R A C I I
API design R C A I I
Cloud infra change I C R A C
Tech stack change I C C R A
Library upgrade R A I I I
4. Architecture Compliance in CI/CD
Automated Architecture Checks Pipeline
yaml# .github/workflows/architecture-check.yml
name: Architecture Compliance
on: [pull_request]
jobs:
architecture-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Run Architecture Tests
run: dotnet test tests/Architecture.Tests --logger trx
- name: Check Dependency Rules
run: dotnet test --filter "Category=DependencyRules"
- name: Check Naming Conventions
run: dotnet test --filter "Category=NamingConventions"
- name: NDepend Analysis (optional)
run: |
# Check for dependency cycles
# Check for layer violations
# Check for complexity thresholds
dotnet ndepend /analyze /project:MyApp.ndproj
What to Automate
| Rule | How to Check | Fail Action |
|---|---|---|
| Layer dependencies | ArchUnitNET tests | Block PR merge |
| Naming conventions | ArchUnitNET + Roslyn Analyzers | Block PR merge |
| Cyclomatic complexity | SonarQube quality gate | Block PR merge |
| Dependency cycles | NDepend / ArchUnitNET | Block PR merge |
| API versioning | Custom middleware test | Block PR merge |
| Database migration safety | Review bot (Flyway validate) | Require manual approval |
5. Evolutionary Architecture Principles
Core Principles
1. Guided Change over Rigid Planning
- Architecture evolves cùng business requirements
- Đừng design cho "5 năm tới" - design cho "6 tháng tới + extensibility"
2. Fitness Functions as Guardrails
- Không dùng tài liệu để enforce - dùng automated tests
- Architecture rules chạy trong CI/CD, mỗi PR
3. Appropriate Coupling
Tight Coupling Loose Coupling
(shared database) (API contracts, events)
↓ ↓
Service A ←→ DB ←→ Service B Service A → Event Bus → Service B
↑ contract test ↑
4. Last Responsible Moment
- Delay architectural decisions cho đến khi có đủ thông tin
- Dùng Spike/PoC để thu thập evidence trước khi commit
5. Build to Replace, Not to Last
- Design services đủ nhỏ để có thể rewrite trong 2-4 tuần
- Nếu service quá lớn để replace → tách nhỏ hơn
Evolutionary Patterns
| Pattern | Mô tả | Dùng khi |
|---|---|---|
| Strangler Fig | Wrap legacy, dần thay thế | Migration legacy → modern |
| Branch by Abstraction | Introduce interface, swap implementation | Thay đổi infrastructure layer |
| Feature Flags | Toggle features at runtime | Gradual rollout, A/B testing |
| Expand-Contract | Add new, migrate, remove old | Database schema evolution |
| Consumer-Driven Contracts | Consumer define expected API | Microservices API evolution |
6. Governance vs Agility Balance
The Governance Spectrum
Full Governance No Governance
(Enterprise/Regulated) (Startup/Chaos)
├────────┼────────┼────────┼────────┤
│ │ │ │ │
Rigid Structured Balanced Light None
ARB for ARB for ADR + Team Everyone
all major fitness autonomy decides
changes changes functions only alone
Finding the Right Balance
| Team Size | Maturity | Recommended Governance |
|---|---|---|
| 1-5 devs | Any | Light - team discussion, ADRs for major decisions |
| 5-15 devs | Low | Structured - ARB for cross-team, fitness functions |
| 5-15 devs | High | Balanced - ADR + fitness functions, autonomous teams |
| 15+ devs | Any | Structured - ARB + automated compliance + ADRs |
| Regulated industry | Any | Full - ARB + audit trail + compliance checks |
Lightweight Governance Framework
markdown## Architecture Governance - Lightweight Version
### Level 1: Automated (Zero human effort)
- ArchUnitNET tests trong CI/CD
- SonarQube quality gates
- Dependency vulnerability scanning
- Naming convention enforcement
### Level 2: Async Review (Low effort)
- ADR cho decisions ảnh hưởng > 1 team
- PR review cho infrastructure changes
- RFC document cho new service/tech adoption
### Level 3: Sync Review (When needed)
- ARB meeting cho strategic decisions
- Design review cho complex features
- Post-incident architecture review
Governance Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Ivory Tower Architecture | Architect quyết định mà không code | Architect phải code ít nhất 30% thời gian |
| Architecture by Committee | Mọi decision cần 10 người approve | Clear RACI - không ai bị block |
| Document-Only Governance | Rules trong wiki mà không ai đọc | Automate rules thành fitness functions |
| No Governance | Mỗi team dùng tech khác nhau, không ai hiểu hệ thống | Minimum ADR + architecture tests |
| Governance Theater | Nhiều process nhưng không enforce | Measure compliance rate, act on violations |
Golden rule: Governance phải giúp team đi nhanh hơn bằng cách giảm rework và alignment cost. Nếu governance chỉ tạo thêm bureaucracy mà không improve outcomes - bỏ đi.
Kết thúc chuỗi Technical Leadership. Xem thêm: SDLC Sessions để bổ sung kiến thức về quy trình phát triển phần mềm.