Skip to main content

Command Palette

Search for a command to run...

Self-managed or DBaaS

Updated
22 min read
N
Già, lười, nói nhiều, đôi lúc hơi khó chịu

Khi Nào Nên DBaaS, Khi Nào Không: Bài Học Từ Ranking Compact Scheduler

Một case study chi tiết về quyết định kiến trúc database — từ self-managed service đến full DBaaS — qua bài toán thực tế: cleanup top trending data trong distributed system. Đặc biệt phân tích mô hình hybrid: common libraries + service consume.


Tóm Tắt

Bài viết phân tích case study thực tế: bài toán compact scheduler cho top trending data, vận dụng các primitive phân tán đã được đóng thành reusable libraries. Từ đó rút ra framework quyết định khi nào nên ở tầng nào của spectrum architecture.

Luận điểm chính:

  1. DBaaS không phải tầng duy nhất giải quyết "shared infrastructure". Library + service consume là một mô hình hybrid mạnh, thường bị bỏ qua.
  2. Một service cụ thể có thể không phải DBaaS nhưng vẫn đóng góp vào hệ sinh thái shared infrastructure qua library.
  3. "Platformize" sớm là over-engineering. Library hóa primitive phân tán thì sớm hay muộn cũng nên làm.

Mục Lục

  1. Bối Cảnh: Bài Toán Top Trending
  2. Kiến Trúc Thực Tế: Service + Library
  3. Phân Tích Hai Tầng Architecture
  4. Spectrum Đầy Đủ: Bốn Tầng
  5. Framework Quyết Định Cho Từng Tầng
  6. Áp Dụng Framework Cho Compact Scheduler
  7. Mô Hình Hybrid: Library + Domain Service
  8. Khi Nào Evolve Lên DBaaS
  9. Anti-Patterns Trong DBaaS Adoption
  10. Bài Học Tổng Quát

1. Bối Cảnh

Bài toán nghiệp vụ

Hệ thống social feed có use case: top trending posts theo nhóm. Mỗi nhóm có một bảng xếp hạng động dựa trên LFUDA algorithm.

Vấn đề khi dữ liệu trending tăng liên tục:

  • Tồn tại nhiều content cũ, lâu không có tương tác
  • Cần cleanup mà không làm cạn cache

Yêu cầu cụ thể

  1. Per-tag threshold: Mỗi (topic, group) phải giữ tối thiểu N items
  2. Hai loại cleanup:
    • Old items (theo key — tuổi đời)
    • Inactive items (theo updatedTime — idle)
  3. Distributed safe: Nhiều pod chạy cùng lúc, không cleanup trùng
  4. Định kỳ: Mỗi 10 phút
  5. Idempotent: Có thể chạy lại nếu fail

Constraint thực tế

  • Storage: MongoDB
  • Coordination: Redis
  • Runtime: Vert.x
  • Team size: 1 team owns trending domain
  • Deployment: Kubernetes, multiple pods

2. Kiến Trúc Thực Tế

Đây không phải "self-contained service"

Khi nhìn vào Compact Scheduler ban đầu, có vẻ là một service đơn lẻ. Nhưng nhìn sâu hơn, kiến trúc thực tế là 2 tầng:

┌────────────────────────────────────────────────────────────────┐
│  Tầng Domain Service (per-team owned)                          │
│                                                                  │
│  ┌──────────────────────────────────┐                          │
│  │ Trending Service                 │                          │
│  │ - GroupRankingPostCompactScheduler│                         │
│  │ - LFUDA cleanup logic            │                          │
│  │ - Business-specific schema       │                          │
│  └──────────────────────────────────┘                          │
│                                                                  │
│  ┌──────────────────────────────────┐                          │
│  │ Notification Service             │                          │
│  │ - Token coordination              │                          │
│  │ - Delivery retry                  │                          │
│  └──────────────────────────────────┘                          │
│                                                                  │
│  ┌──────────────────────────────────┐                          │
│  │ Statistics Service               │                          │
│  │ - ActiveUsersCountService         │                          │
│  │ - Singleton job per day           │                          │
│  └──────────────────────────────────┘                          │
└──────────────────────┬─────────────────────────────────────────┘
                       │
                       │ depends on
                       ▼
┌────────────────────────────────────────────────────────────────┐
│  Tầng Shared Library (cross-team reusable)                     │
│                                                                  │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │ Distributed Primitives Library                            │ │
│  │                                                            │ │
│  │ - RedisAsyncDelayedQueue                                   │ │
│  │ - RedisAsyncDistributedLock                                │ │
│  │ - RedisAsyncSortedSet (with putNX)                         │ │
│  │ - RedisAsyncQueue                                          │ │
│  │ - AbstractDistributedPeriodicJobService                    │ │
│  │ - AbstractDistributedSingletonJobService                   │ │
│  │ - AbstractRedisAsyncExpiringMap                            │ │
│  │ - VertxTaskWrapper                                         │ │
│  └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘

Vai trò của library tầng dưới

Library này không phải DBaaS nhưng cung cấp shared infrastructure capabilities:

  • Distributed coordination primitives: lock, queue, leader election
  • Time-based scheduling: delayed queue, periodic job, singleton job
  • State management: expiring map, sorted set với putNX

Điểm quan trọng: Library này được dùng bởi nhiều service khác nhau trong tổ chức.

Vai trò của Compact Scheduler

một consumer của library, đồng thời là một implementation cụ thể cho domain trending:

public class GroupRankingPostCompactSchedulerModel 
    extends AbstractDistributedPeriodicJobService<Void> {
    // ↑ Reuse pattern từ library
    
    // Logic specific cho trending
    private final RedisAsyncDelayedQueue uniqueTopicIdGroupIdEntriesQueue;
    // ↑ Reuse data structure từ library
    
    @Override
    protected Future<Void> doWork(MomoData input) {
        // LFUDA cleanup specific cho trending
        return doRemove(input);
    }
}

Service domain reuse library primitives, không phải build lại từ đầu.


3. Hai Tầng Architecture

Đặc tính:

  • Specific cho domain trending
  • LFUDA + per-tag threshold là business logic của team
  • Schema MongoDB là quyết định của domain
  • Cleanup policy (10 phút, sequential overflow→inactive) là nghiệp vụ

Không thể chia sẻ vì:

  • Schema khác nhau giữa domains
  • Cleanup criteria khác nhau (trending vs notification vs statistics)
  • SLA khác nhau

Tầng 2: Shared Library (Cross-team)

Đặc tính:

  • Generic distributed primitives
  • Không bound vào business domain
  • Stable API, ít thay đổi
  • Maintained chung (có thể bởi platform team hoặc rotate ownership)

Có thể chia sẻ vì:

  • Đều là pattern phân tán cơ bản
  • Independent với business context
  • Standardize coordination across services

Tại sao mô hình này hiệu quả

Single Responsibility ở mỗi tầng:

Library tầng dưới:
  Responsibility = distributed coordination primitives
  Stable, ít breaking change
  Optimize cho correctness + performance

Domain service tầng trên:
  Responsibility = business logic
  Thay đổi nhanh theo nghiệp vụ
  Optimize cho domain-specific case

Separation of Concerns rõ ràng:

Khi cần thêm cleanup pattern mới:
  - Cùng tầng: thêm Domain Service mới, reuse library

Khi cần improve distributed primitive:
  - Library tầng dưới: update version, tất cả domain hưởng lợi

Khi cần thay đổi business rule:
  - Chỉ Domain Service thay đổi, library không touch

Sustainability cao:

Domain knowledge: trong team domain (đúng người)
Infrastructure knowledge: trong library + maintainer (centralized)
Không có team nào ôm tất cả → scale được

4. Spectrum Bốn Tầng

Định nghĩa lại spectrum đầy đủ

                    Architecture Spectrum
                                                          
Tầng 0           Tầng 1           Tầng 2            Tầng 3
Pure             Library          Platform          Full
Domain Service   + Domain         Service           DBaaS
                 Service                            
─────────────────────────────────────────────────────────
                                                          
1 team           Multi-team       Multi-team        Multi-team
Build from       Library shared   Service shared    Storage primitives shared
scratch          Service-specific (domain still     (no domain logic)
                                  in service)
                                                          
No shared        Shared code      Shared runtime    Shared infrastructure
infra            Independent      Centralized       Fully managed
                 runtime          API               
                                                          
Examples:        Examples:        Examples:         Examples:
- Greenfield     - Compact        - Notification    - MongoDB Atlas
  prototype       Scheduler        Service           - DynamoDB
- 1-off script   - ActiveUsers    - Token Service                            
                  CountService                       

Vị trí của Compact Scheduler

  Tầng 0: Pure Domain Service
✓ Tầng 1: Library + Domain Service ◄── HIỆN TẠI
  Tầng 2: Platform Service
  Tầng 3: Full DBaaS

Compact Scheduler không phải Tầng 0 vì có reuse library cross-team.

Cũng không phải Tầng 2 vì không expose API runtime cho team khác consume — team khác chỉ import library, tự deploy service của mình.

Tầng 1 là sweet spot.

Đặc trưng của Tầng 1 (Library + Domain Service)

Ưu điểm:

  • Code reuse ở mức infrastructure
  • Mỗi domain own service riêng → ownership rõ
  • Library stable, ít breaking change
  • Không cần platform team dedicated
  • Performance tối ưu (in-process call, không cross-service)

Nhược điểm:

  • Library upgrade phải coordinate (nhưng hiếm)
  • Mỗi service phải tự deploy/maintain runtime
  • Không có self-service config cross-team
  • Monitoring/audit phân tán theo service

5. Framework Quyết Định

Tiêu chí 1: Library extraction (Tầng 0 → 1)

Tình huống Approach
Pattern xuất hiện 1 lần Tầng 0 — chưa cần extract
Pattern xuất hiện 2 lần Cân nhắc extract
Pattern xuất hiện 3+ lần Bắt buộc extract thành library

Logic: Rule of Three. Trùng lặp code 3 chỗ → maintenance nightmare.

Tiêu chí 2: Số team consume library

Số team Approach
1 team Tầng 0 (chưa cần library)
2-N teams Tầng 1 (library shared)
Tất cả teams Cân nhắc Tầng 2 (platform service)

Tiêu chí 3: Domain logic vs Infrastructure logic

Tình huống Tầng phù hợp
Domain logic chiếm 80%+ Tầng 1 (giữ domain ownership)
Infrastructure logic chiếm 50%+ Tầng 2 (centralize infrastructure)
Pure infrastructure (storage) Tầng 3 (DBaaS)

Tiêu chí 4: Library evolution cost

Tần suất update library Approach
Rất stable (vài tháng/lần) Tầng 1 OK
Hay update (hàng tuần) Tầng 2 (centralize runtime để rollout dễ)
Cần atomic upgrade cross-team Tầng 2-3

Logic: Library upgrade tốn coordination. Nếu thường xuyên → service share cùng runtime tốt hơn.

Tiêu chí 5: Operational overhead

Operations Tầng phù hợp
Mỗi team OK tự deploy/operate Tầng 1
Cần centralized monitoring/alerting Tầng 2
Cần SLA, compliance, audit Tầng 3

Tiêu chí 6: Performance requirement

Latency Approach
<10ms critical Tầng 1 (in-process call)
10-100ms OK Tầng 1-2
Eventual consistency OK Tầng 2-3

Tiêu chí 7: ROI

Tầng 1 (Library): low cost, low/medium benefit, positive ROI when N≥2
Tầng 2 (Platform): high cost, high benefit, positive ROI when N≥4-6
Tầng 3 (DBaaS): very high cost, very high benefit, positive ROI when N≥7+

6. Áp Dụng Framework

Đánh giá hiện trạng

Tiêu chí Compact Scheduler Verdict
Library extraction Đã extract: DelayedQueue, Lock, AbstractPeriodicJob ✅ Tầng 1
Số team consume library 3+ services (Trending, Notification, Statistics) ✅ Tầng 1
Domain logic vs Infra LFUDA business logic chiếm 70% Compact Scheduler ✅ Giữ domain
Library evolution Stable, ít update ✅ Tầng 1 phù hợp
Operational overhead Mỗi service tự operate OK ✅ Tầng 1 phù hợp
Performance Background job, latency không critical ✅ Tầng 1 OK
ROI 3 services share library → positive ✅ Tầng 1 đúng

Verdict: Tầng 1 (Library + Domain Service) là đúng cho hiện tại.

Tại sao không phải Tầng 0?

Vì pattern đã xuất hiện ở nhiều service:

  • AbstractDistributedPeriodicJobService: dùng bởi Compact Scheduler và các periodic job khác
  • AbstractDistributedSingletonJobService: dùng bởi ActiveUsersCountService và các daily job khác
  • RedisAsyncDelayedQueue, RedisAsyncDistributedLock: dùng cross-service

→ Library hóa là đúng đắn, không phải over-engineering.

Tại sao không phải Tầng 2?

Vì:

  • Domain logic mỗi service khác nhau (LFUDA vs token coordination vs activity counting)
  • Không có team yêu cầu self-service config qua API
  • Mỗi service tự deploy OK, không cần centralized runtime
  • Performance tốt với in-process library call

→ Build Platform Service sẽ over-engineer.

Tại sao không phải Tầng 3?

Vì:

  • Storage (MongoDB, Redis/Pika) đã được provision và quản lý ở infrastructure layer riêng
  • Compact Scheduler không expose storage primitives
  • Schema fix theo domain, không cần generic storage abstraction
  • Compliance/audit chưa yêu cầu unified control plane

→ DBaaS không có ROI.


7. Mô Hình Hybrid

Tại sao Tầng 1 (Library + Domain Service) thường bị overlook

Khi nói về "platform" hay "shared infrastructure", developers thường nghĩ ngay đến:

  • Microservice riêng cho cross-team capability
  • API gateway centralize access
  • DBaaS hoặc managed service

→ Bỏ qua mô hình library vì có vẻ "kém phức tạp".

Nhưng thực tế, library hóa primitive phân tán là mô hình stable và effective nhất trong nhiều case:

Lợi thế của library so với service riêng

1. Không có network overhead

Library call:      pod → library function → result
Service call:      pod → network → service → network → result

Service riêng = thêm 2 network hop. Cho coordination primitive (lock, queue), latency rất quan trọng.

2. Không có cascading failure

Library: lib bug → service consume bị ảnh hưởng nhưng isolated
Service: shared service down → tất cả consumer down

3. Không cần platform team riêng

Library: maintainer rotate giữa các team
Service: cần dedicated team on-call

4. Versioning rõ ràng

Library: import version specific, control rollout per service
Service: tất cả consumer dùng cùng version, breaking change khó

5. Performance tối ưu hơn

Library: trong cùng JVM, không serialize
Service: cross-process serialize/deserialize

Nhược điểm của library

1. Coordination khi update

Library v1 → v2 với breaking change → cần coordinate update giữa các team.

Mitigation: semver chặt chẽ, backward compatibility, deprecation period.

2. Distributed state vẫn cần shared infra

Library chỉ là code. State (Redis, MongoDB) vẫn phải shared.

Mitigation: library wrap shared infra cleanly, infra layer riêng.

3. Khó observability cross-team

Mỗi service log/monitor riêng → khó nhìn toàn cảnh.

Mitigation: library emit metrics theo convention chung, aggregation ở observability layer.

Compact Scheduler là minh chứng

Library reuse rõ ràng:

// Library code (shared)
public abstract class AbstractDistributedPeriodicJobService<T> {
    protected final RedisAsyncDelayedQueue delayedQueue;
    protected final RedisAsyncDistributedLock distributedLock;
    // ... distributed primitive logic
}

// Domain service (Compact Scheduler)
public class GroupRankingPostCompactSchedulerModel 
    extends AbstractDistributedPeriodicJobService<Void> {
    
    @Override
    protected Future<Void> doWork(MomoData input) {
        // Business logic LFUDA, MongoDB cleanup
    }
}

Library cũng được dùng bởi service khác:

// Service khác (ActiveUsersCountService)
public class ActiveUsersCountService 
    extends AbstractDistributedSingletonJobService<List<GrpcScribeLogEntry>> {
    
    @Override
    protected Future<List<GrpcScribeLogEntry>> doWork(MomoData input) {
        // Business logic count active users
    }
}

Cùng library, khác business logic. Đây chính là power của Tầng 1.


8. Evolve Lên DBaaS

Khi nào Tầng 1 không còn đủ

Library + Domain Service không phải silver bullet. Có những signals thực sự để evolve lên Tầng 2 hoặc Tầng 3:

Signals evolve lên Tầng 2 (Platform Service)

1. Cross-team policy enforcement

Tất cả periodic job phải có rate limit, retry policy, dead letter — không thể leave cho mỗi team tự decide.

→ Centralize policy enforcement qua service runtime.

2. Operational complexity tăng

Mỗi team deploy library riêng → 50 service instances → operational nightmare.

→ Centralize thành service shared runtime.

3. Dynamic config cross-team

Cần thay đổi threshold, schedule cho nhiều domain từ 1 chỗ.

→ Self-service config UI/API.

4. Multi-tenancy isolation chặt

Compliance/security yêu cầu strict isolation giữa các tenant.

→ Service với namespace-based isolation.

Signals evolve lên Tầng 3 (DBaaS)

1. Storage operations là bottleneck

Cleanup, replication, sharding, backup phức tạp đến mức cần expert team chuyên trách.

→ Full managed storage service.

2. Compliance/Audit unified

GDPR, SOC2 yêu cầu unified data lineage, retention policy, access control.

→ Centralized data platform.

3. Cost optimization at scale

Storage cost lớn đến mức cross-domain optimization (deduplication, compression, tiered storage) tiết kiệm đáng kể.

→ Platform team với storage expertise.

4. Multiple storage technologies

Mỗi domain dùng storage khác (Mongo, Cassandra, Redis, S3) → caller không thể own all expertise.

→ DBaaS abstraction.

Compact Scheduler hiện tại

Chưa có signal nào trong số trên với mức độ đáng kể. Tầng 1 vẫn phù hợp.

Khi nào trigger evolve?

Trigger Tầng 1 → Tầng 2:
  - 10+ services dùng library với customization phức tạp
  - Cần unified retry/dead-letter policy cross-team
  - Có platform team dedicated muốn own runtime

Trigger Tầng 2 → Tầng 3:
  - Storage cost trở thành top business concern
  - Compliance requirement bắt buộc
  - Multiple storage technologies cần unified interface

9. Anti-Patterns

Anti-pattern 1: Bypass Library, Build Direct

Team mới không biết library → reimplement primitive phân tán:

// ❌ Anti-pattern: reimplement
public class MyTeamScheduler {
    private final RedisAPI redisAPI;
    
    public void run() {
        vertx.setPeriodic(60_000, t -> {
            // Tự implement distributed lock
            redisAPI.set(...).onSuccess(...);
            // Tự implement queue
            // Tự implement watchdog
            // ... 500 dòng code
        });
    }
}

→ Mất lợi ích của library, duplicate bug, không học từ kinh nghiệm.

Fix: Onboarding tốt, document library, code review enforce.

// ✅ Đúng: reuse library
public class MyTeamScheduler 
    extends AbstractDistributedPeriodicJobService<Void> {
    @Override
    protected Future<Void> doWork(MomoData input) {
        // Chỉ business logic
    }
}

Anti-pattern 2: Library Bloat

Thêm features vào library cho 1 use case cụ thể của 1 team:

// Team A muốn feature X cho use case riêng
// → thêm vào library
public abstract class AbstractDistributedPeriodicJobService<T> {
    // Original methods...
    
    // Added for team A's specific case
    protected void teamASpecificHook() { ... }
    
    // Added for team B's specific case
    protected void teamBSpecificHook() { ... }
    
    // ... library trở thành God Class
}

→ Library mất tính generic, khó maintain.

Fix: Library chỉ generic primitives. Use case specific stay in domain service.

Anti-pattern 3: Library Versioning Lỏng Lẻo

// Library v1.5 breaking change
@Override
protected Future<T> doWork(MomoData input) → 
protected Future<Result<T>> doWork(JobContext context)

// Không có migration path, không backward compatible
// → tất cả service phải refactor cùng lúc

Fix: Semantic versioning, deprecation period, migration guide.

Anti-pattern 4: Force Platformization

Thấy nhiều team dùng library → ép evolve thành Platform Service:

Trước: Library + 5 service instances
Sau:  Platform service centralized
       + migration của 5 service
       + cross-team coordination
       + new operational complexity

→ Nếu library đang ổn, đừng force evolve. ROI âm.

Fix: Evolve khi có signal thật (cost, compliance, scale), không phải vì principle.

Anti-pattern 5: Skipping Library Stage

Stage 0 (Direct) ──[skip]──► Stage 2 (Platform Service)

Bỏ qua library stage, build service ngay → tốn cost vô lý cho 2-3 consumers.

Fix: Always evolve through stages. Library trước, service sau.


10. Bài Học

Bài học 1: Library là tầng bị overlook

Khi nói "shared infrastructure", developers nghĩ ngay đến service riêng hoặc DBaaS. Bỏ qua library.

Library + Domain Service là mô hình hybrid mạnh trong nhiều case:

  • Không network overhead
  • Domain ownership rõ ràng
  • Sustainable không cần platform team
  • Versioning controlled

Bài học 2: Spectrum không phải binary

Không phải "có DBaaS hay không có DBaaS". Có 4 tầng với trade-offs khác nhau:

Tầng 0: Pure Domain (1 team, prototype)
Tầng 1: Library + Domain (multi-team, library shared)
Tầng 2: Platform Service (multi-team, runtime shared)
Tầng 3: Full DBaaS (multi-team, storage shared)

Chọn tầng đúng với context, không phải tầng "fancy" nhất.

Bài học 3: Library hóa primitive phân tán

Distributed primitives (lock, queue, leader election) luôn luôn đáng library hóa:

  • Khó implement đúng
  • Easy to mess up
  • Pattern stable
  • Cross-team reuse value cao

Compact Scheduler đúng vì leverage library hiện có. Đừng reimplement.

Bài học 4: Domain logic ở domain, infra logic ở library

Separation rõ ràng:

Library: distributed primitive (generic)
Domain Service: business logic (specific)

Đừng đẩy domain logic xuống library (bloat). Đừng đẩy infra logic lên domain (duplicate).

Bài học 5: Rule of Three vẫn quan trọng

Library hóa khi pattern xuất hiện 3 lần. Không sớm hơn.

Compact Scheduler có lợi vì library đã đủ mature (3+ consumers).

Bài học 6: Versioning library là kỹ năng quan trọng

Library serve multiple teams → versioning chặt chẽ:

  • Semantic versioning
  • Backward compatibility
  • Deprecation period
  • Migration guide cho breaking change

Skill này thường bị underestimate nhưng critical cho Tầng 1.

Bài học 7: Architectural decisions are economic decisions

Mỗi tầng có cost-benefit khác nhau:

Tầng 0: cost low, benefit limited (1 team)
Tầng 1: cost medium, benefit high (N teams reuse)
Tầng 2: cost high, benefit very high (centralized ops)
Tầng 3: cost very high, benefit highest (full managed)

Chọn tầng với ROI dương cao nhất.

Bài học 8: "Right-sized" architecture

Compact Scheduler là ví dụ right-sized:

  • Library reuse: không reinvent infrastructure
  • Domain service: own business logic
  • Performance: in-process, fast
  • Maintenance: team owns full stack of business

Đây là production-grade architecture, không phải "thiếu DBaaS".


Kết Luận

Trả lời câu hỏi gốc

"Compact Scheduler có phải DBaaS không?"Không.

"Có phải self-contained service không?"Không hẳn — nó là Domain Service consume Shared Library.

"Có nên evolve thành DBaaS không?"Hiện tại không cần. Tầng 1 (Library + Domain Service) là sweet spot.

"Đây có phải design tốt không?"Có. Đây là production-grade hybrid model.

Framework Quyết Định Trong 1 Câu

"Library hóa primitive khi pattern xuất hiện 3+ lần. Service hóa khi cần centralized runtime. DBaaS khi cần managed storage. Chọn tầng thấp nhất đủ đáp ứng, evolve khi có signal thật."

Decision Tree Tổng Kết

Bài toán có cần distributed coordination/storage capability?
│
└── Có ──► Pattern đã xuất hiện ở bao nhiêu chỗ?
          │
          ├── 1 chỗ ──► Tầng 0: Build trong service, chưa cần library
          │
          ├── 2 chỗ ──► Cân nhắc Tầng 1: extract library nếu pattern stable
          │
          ├── 3-5 chỗ ──► Tầng 1: Library + Domain Service ◄── SWEET SPOT
          │              (Compact Scheduler ở đây)
          │
          ├── 5-10 chỗ + customization ──► Cân nhắc Tầng 2: Platform Service
          │
          ├── 10+ chỗ + compliance/scale ──► Tầng 3: DBaaS
          │
          └── Storage primitives expose ──► Tầng 3: DBaaS

Lời Cuối

Architecture không phải về việc dùng tầng cao nhất. Architecture là về right-sized solution cho concrete problem.

Compact Scheduler là minh chứng:

  • Không phải DBaaS — và đó là điều tốt
  • Không phải pure domain service — đã leverage library
  • Là hybrid model — domain logic + shared infrastructure library

Đây là sweet spot ít được nói đến nhưng cực kỳ effective. Library + Domain Service giải quyết hầu hết bài toán distributed coordination trong tổ chức tầm trung, không cần platform team riêng, không cần DBaaS.

Đừng để pressure of "best practice" buộc bạn over-engineer. Library hóa khi cần, service hóa khi đáng, DBaaS khi bắt buộc. Mỗi tầng có vai trò của nó.


Bài viết tổng hợp từ kinh nghiệm thực tế xây dựng Compact Scheduler trong hệ thống feedv2 tại MService. Phản ánh kiến trúc thật: domain service consume shared library distributed primitives.


Phụ Lục: Checklist Quyết Định Architecture Layer

Câu hỏi 1: Tầng 0 hay Tầng 1?

  • Pattern đã xuất hiện ở 3+ services chưa?
  • Pattern có stable interface (ít breaking change) không?
  • maintainer willing own library không?
  • Versioning strategy rõ ràng chưa?

Nếu yes hết → extract library (Tầng 1).

Câu hỏi 2: Tầng 1 hay Tầng 2?

  • 5+ services đang consume library?
  • Cần centralized policy enforcement không?
  • platform team maintain runtime forever không?
  • Operational complexity của distributed library đã tăng đáng kể chưa?
  • Có yêu cầu self-service config cross-team không?

Nếu yes phần lớn → cân nhắc Platform Service (Tầng 2).

Câu hỏi 3: Tầng 2 hay Tầng 3?

  • Storage cost là top business concern không?
  • Compliance/Audit unified bắt buộc không?
  • multiple storage technologies cần abstract không?
  • Build + maintain DBaaS có dedicated team chưa?
  • ROI cho 7+ consumers tính đúng chưa?

Nếu yes phần lớn → cân nhắc Full DBaaS (Tầng 3).

Nếu không yes nhiều câu nào → đừng evolve.


Phụ Lục 2: Identifying Library Candidates

Pattern nào trong codebase nên library hóa? Tìm các đặc điểm:

Tín hiệu nên library hóa

Pattern xuất hiện 3+ lần ở các service khác nhau

Logic không thay đổi theo domain (distributed lock không quan tâm bạn lock cái gì)

Khó implement đúng (race condition, edge case nhiều)

Interface stable (input/output ít thay đổi)

Test phức tạp (cần infrastructure test)

Failure mode tinh tế (cần expertise để biết)

Tín hiệu không nên library hóa

❌ Logic thay đổi theo domain (LFUDA cleanup, business validation)

❌ Pattern mới, chưa stable

❌ Chỉ 1-2 use case

❌ Easy to implement, ít edge case

❌ Interface đang flux

Examples trong feedv2

Nên library hóa (đã làm):

  • Distributed Lock
  • Delayed Queue
  • Periodic Job Service abstract
  • Singleton Job Service abstract
  • Expiring Map

Không nên library hóa (giữ trong domain):

  • LFUDA cleanup logic
  • Top trending calculation
  • Active users counting
  • Notification delivery logic
  • Token revocation logic

Phụ Lục 3: Reading List

Cho ai muốn đào sâu:

  • "Domain-Driven Design" — Eric Evans (domain vs infrastructure separation)
  • "Team Topologies" — Skelton & Pais (team structure cho platform teams)
  • "Building Evolutionary Architectures" — Neal Ford et al. (architectural fitness)
  • "The Wrong Abstraction" — Sandi Metz (về abstraction debt)
  • Martin Fowler — "Inner Source" (về library hóa nội bộ)
  • "Patterns of Enterprise Application Architecture" — Martin Fowler (về layered architecture)

Đặc biệt "Inner Source" giải thích rõ mô hình Library + Domain Service trong organizations.

5 views