Self-managed or DBaaS
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:
- 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.
- 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.
- "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
- Bối Cảnh: Bài Toán Top Trending
- Kiến Trúc Thực Tế: Service + Library
- Phân Tích Hai Tầng Architecture
- Spectrum Đầy Đủ: Bốn Tầng
- Framework Quyết Định Cho Từng Tầng
- Áp Dụng Framework Cho Compact Scheduler
- Mô Hình Hybrid: Library + Domain Service
- Khi Nào Evolve Lên DBaaS
- Anti-Patterns Trong DBaaS Adoption
- 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ể
- Per-tag threshold: Mỗi (topic, group) phải giữ tối thiểu N items
- Hai loại cleanup:
- Old items (theo
key— tuổi đời) - Inactive items (theo
updatedTime— idle)
- Old items (theo
- Distributed safe: Nhiều pod chạy cùng lúc, không cleanup trùng
- Định kỳ: Mỗi 10 phút
- 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
Là 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
Tầng 1: Domain Service (Trending team)
Đặ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?
- Có 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?
- Có 5+ services đang consume library?
- Cần centralized policy enforcement không?
- Có 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?
- Có 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.
