LeadershipTechnical ManagerTechnical Leadership

Session 04 - Architecture Governance

"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

bash
dotnet add package TngTech.ArchUnitNET.xUnit

Layer Dependency Rules

csharp
using 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)

AspectDetail
MembersCTO/VP Eng, Senior Architects, Tech Leads (rotating)
FrequencyBi-weekly hoặc monthly
Duration60 phút
InputArchitecture Decision Records (ADR), RFC documents
OutputApprove / 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 TypeVí dụDecision MakerConsultedInformed
Strategic ArchitectureMonolith vs Microservices, Cloud providerCTO + ARBTech Leads, POAll engineering
Service ArchitectureDatabase choice per service, API styleTech Lead + ARBSenior devs, DevOpsTeam members
Module DesignDesign patterns, class structureSenior DevTeam membersTech Lead
ImplementationAlgorithm choice, library selectionDeveloperPeer (via PR review)Tech Lead
Coding StandardsNaming, formatting, lint rulesTech Lead + Team consensusAll devsNew joiners
Emergency ChangesProduction hotfix architectureOn-call Senior + Tech LeadEngineering ManagerARB (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

RuleHow to CheckFail Action
Layer dependenciesArchUnitNET testsBlock PR merge
Naming conventionsArchUnitNET + Roslyn AnalyzersBlock PR merge
Cyclomatic complexitySonarQube quality gateBlock PR merge
Dependency cyclesNDepend / ArchUnitNETBlock PR merge
API versioningCustom middleware testBlock PR merge
Database migration safetyReview 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

PatternMô tảDùng khi
Strangler FigWrap legacy, dần thay thếMigration legacy → modern
Branch by AbstractionIntroduce interface, swap implementationThay đổi infrastructure layer
Feature FlagsToggle features at runtimeGradual rollout, A/B testing
Expand-ContractAdd new, migrate, remove oldDatabase schema evolution
Consumer-Driven ContractsConsumer define expected APIMicroservices 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 SizeMaturityRecommended Governance
1-5 devsAnyLight - team discussion, ADRs for major decisions
5-15 devsLowStructured - ARB for cross-team, fitness functions
5-15 devsHighBalanced - ADR + fitness functions, autonomous teams
15+ devsAnyStructured - ARB + automated compliance + ADRs
Regulated industryAnyFull - 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-PatternProblemFix
Ivory Tower ArchitectureArchitect quyết định mà không codeArchitect phải code ít nhất 30% thời gian
Architecture by CommitteeMọi decision cần 10 người approveClear RACI - không ai bị block
Document-Only GovernanceRules trong wiki mà không ai đọcAutomate rules thành fitness functions
No GovernanceMỗi team dùng tech khác nhau, không ai hiểu hệ thốngMinimum ADR + architecture tests
Governance TheaterNhiều process nhưng không enforceMeasure 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.