- [[#Core Message|Core Message]] - [[#Incremental Migration|Incremental Migration]] - [[#Incremental Migration#Why Incremental?|Why Incremental?]] - [[#Incremental Migration#The Stock Advice|The Stock Advice]] - [[#The Monolith Is Rarely the Enemy|The Monolith Is Rarely the Enemy]] - [[#The Dangers of Premature Decomposition|The Dangers of Premature Decomposition]] - [[#The Dangers of Premature Decomposition#The Snap CI Story|The Snap CI Story]] - [[#What to Split First?|What to Split First?]] - [[#What to Split First?#Two Forces to Balance|Two Forces to Balance]] - [[#What to Split First?#Goal-Driven Prioritization|Goal-Driven Prioritization]] - [[#What to Split First?#Practical Advice|Practical Advice]] - [[#Decomposition by Layer|Decomposition by Layer]] - [[#Decomposition by Layer#UI Decomposition|UI Decomposition]] - [[#Decomposition by Layer#Code vs. Data: Which First?|Code vs. Data: Which First?]] - [[#Code vs. Data: Which First?#Code First (More Common)|Code First (More Common)]] - [[#Code vs. Data: Which First?#Data First (Less Common)|Data First (Less Common)]] - [[#Useful Decompositional Patterns|Useful Decompositional Patterns]] - [[#Useful Decompositional Patterns#Strangler Fig Pattern|Strangler Fig Pattern]] - [[#Useful Decompositional Patterns#Parallel Run|Parallel Run]] - [[#Useful Decompositional Patterns#Feature Toggle|Feature Toggle]] - [[#Data Decomposition Concerns|Data Decomposition Concerns]] - [[#Data Decomposition Concerns#1. Performance|1. Performance]] - [[#Data Decomposition Concerns#2. Data Integrity|2. Data Integrity]] - [[#Data Decomposition Concerns#3. Transactions|3. Transactions]] - [[#Data Decomposition Concerns#4. Tooling|4. Tooling]] - [[#Reporting Database Pattern|Reporting Database Pattern]] - [[#Summary: Migration Checklist|Summary: Migration Checklist]] - [[#Discussion Questions|Discussion Questions]] ## Core Message **Microservices are not the goal.** You don't "win" by having microservices. Before migrating, ask: 1. What are you trying to achieve? 2. Is there an easier way to achieve it with your current architecture? > "Microservices aren't easy. Try the simple stuff first." **Example:** Want to scale? Try spinning up more copies of your monolith behind a load balancer before decomposing into microservices. --- ## Incremental Migration > "If you do a big-bang rewrite, the only thing you're guaranteed of is a big bang." — Martin Fowler ### Why Incremental? | Benefit | Description | |---------|-------------| | **Learn as you go** | Microservices have surprises; discover them in small doses | | **Limit blast radius** | If something goes wrong, it's a small step back | | **Quick wins** | Build momentum with early successes | | **Unlock value incrementally** | Don't wait for a big bang deployment | ### The Stock Advice > "Start somewhere small. Choose one or two areas of functionality, implement them as microservices, get them deployed into production, and then reflect on whether it helped." **Warning:** > "You won't appreciate the true horror, pain, and suffering that a microservice architecture can bring until you are running in production." --- ## The Monolith Is Rarely the Enemy - A monolithic architecture isn't inherently bad - Don't focus on "not having the monolith"—focus on the benefits you expect - Monolith and microservices often coexist (and that's fine) - "Messy" coexistence is reality; laminate your clean architecture diagram if you want pristine **Common outcome:** Extract the 10% causing problems, leave 90% in the monolith. **When full removal is required:** - Dead/dying technology - Infrastructure being retired - Expensive third-party system you want to ditch Even then, incremental approach is still warranted. --- ## The Dangers of Premature Decomposition ### The Snap CI Story Thoughtworks built Snap CI (hosted CI/CD tool) after building GoCD. The team's domain experience led them to define microservice boundaries early. **What happened:** - Use cases turned out to be subtly different - Initial service boundaries were wrong - Lots of cross-service changes = high cost of change - **Solution:** Merged back into a monolith, waited a year, then re-split with stable boundaries **Lesson:** > "Prematurely decomposing a system into microservices can be costly, especially if you are new to the domain." Having an existing codebase to decompose is actually *easier* than greenfield microservices—you already understand the domain. --- ## What to Split First? ### Two Forces to Balance | Force | Description | |-------|-------------| | **Benefit** | How much does extracting this help achieve our goal? | | **Ease** | How difficult is the extraction? | ### Goal-Driven Prioritization | Goal | What to Extract | |------|-----------------| | **Scale** | Functionality that constrains load handling | | **Time to market** | Volatile parts that change frequently | **Tools:** Static analysis tools like CodeScene can identify hotspots (frequently changing code). ### Practical Advice For your **first couple of microservices**: - Lean toward the "easy" end - Pick low-hanging fruit with *some* impact on your goal - Build momentum with quick wins - Learn lessons before tackling complex extractions **Reality check:** > "If you try to extract what you consider to be the easiest microservice and aren't able to make it work, it might be worth reconsidering whether microservices are really right for you." --- ## Decomposition by Layer When extracting a microservice, consider three tiers: 1. **User Interface** 2. **Backend application code** 3. **Data** ### UI Decomposition - Often lags behind backend decomposition - Don't ignore it—sometimes the biggest benefits come from UI decomposition - Hard to see possibilities until microservices exist ### Code vs. Data: Which First? #### Code First (More Common) ``` Step 1: Extract wishlist code → new microservice Step 2: Data stays in monolith database (temporarily) Step 3: Extract data later ``` **Why this is common:** - Delivers short-term benefit faster - Extracting code is usually easier than database - If code extraction fails, abort before tackling database **Critical:** Even if you extract code first, **do the legwork upfront** to understand if data extraction is viable. #### Data First (Less Common) ``` Step 1: Extract wishlist tables → separate database Step 2: Code still in monolith Step 3: Extract code later ``` **Why you might do this:** - Derisk the full extraction - Forces you to confront data integrity and transaction issues early - Proves data separation is possible before investing in code changes --- ## Useful Decompositional Patterns ### Strangler Fig Pattern Named after a plant that wraps around a tree and eventually replaces it. **How it works:** 1. Intercept calls to the monolith 2. If functionality exists in new microservice → redirect there 3. If not → let call continue to monolith ``` ┌─────────────┐ Incoming Request → │ Proxy │ └──────┬──────┘ │ ┌────────────┴────────────┐ ▼ ▼ ┌──────────┐ ┌──────────┐ │Microservice│ │ Monolith │ └──────────┘ └──────────┘ ``` **Key benefit:** Can often be done without changing the monolith at all. ### Parallel Run Run both implementations side by side: - Monolith handles request - Microservice handles same request - Compare results **Use case:** Critical functionality where you need confidence the new implementation works correctly. ### Feature Toggle Switch between implementations (monolith vs. microservice) with a flag. - Can implement in the proxy layer (with strangler fig) - Allows controlled rollout - Easy rollback if problems occur --- ## Data Decomposition Concerns Breaking apart databases introduces new challenges: ### 1. Performance **Problem:** Databases are *really good* at joins. You won't be. **Example:** Monthly bestsellers report *Before (single database):* ```sql SELECT album.name, COUNT(*) FROM ledger JOIN albums ON ledger.sku = albums.sku GROUP BY album.name ORDER BY COUNT(*) DESC ``` *After (separate databases):* 1. Finance service queries Ledger table → list of SKUs 2. Finance service calls Catalog service with SKUs 3. Catalog service queries Albums table 4. Finance service combines results in application code **Reality:** This will be slower. Mitigations: - Bulk lookups instead of individual calls - Caching - Accepting the latency for infrequent operations ### 2. Data Integrity **Problem:** No more foreign key constraints across services. *Before:* Foreign key from Ledger.sku → Albums.sku prevents orphaned records *After:* Nothing stops you from deleting an album that's referenced in ledger **Coping patterns:** - Soft deletes (mark as deleted, don't remove) - Copy data at write time (denormalization) - Accept eventual consistency ### 3. Transactions **Problem:** No more ACID transactions across services. > "For people moving from a system in which all state changes could be managed in a single transactional boundary, the shift to distributed systems can be a shock." **Reality:** - Distributed transactions are complex and don't give same guarantees - Alternative: Sagas (covered in Chapter 6) - Must accept new sources of complexity ### 4. Tooling **Problem:** Database refactoring tools are limited compared to code refactoring. **Recommended tools:** - Flyway - Liquibase - Rails migrations (or similar delta script approach) **Pattern:** Version-controlled delta scripts run in strict order, idempotently. --- ## Reporting Database Pattern **Problem:** You've hidden direct database access (good!), but legitimate use cases need data from multiple microservices in database form. **Solution:** Create a dedicated external-facing reporting database. ``` ┌─────────────┐ ┌──────────────────┐ │ Microservice│ ───► │ Reporting Database│ ◄─── External Consumers │ (internal │ │ (external) │ (SQL queries, BI tools) │ storage) │ └──────────────────┘ └─────────────┘ ``` **Key points:** 1. **Still practice information hiding** - Expose only minimum necessary data - Schema can be subset of internal data - Can use different schema design or even different database technology 2. **Treat as a microservice endpoint** - Maintainers responsible for compatibility - Internal changes shouldn't break external contract - Mapping from internal → reporting is the team's responsibility --- ## Summary: Migration Checklist 1. Clear goal (not "have microservices") 2. Considered simpler alternatives first 3. Incremental approach planned 4. First extraction is low-hanging fruit 5. Both code AND data extraction sketched out 6. Patterns identified (strangler fig, feature toggle, etc.) 7. Data concerns addressed (joins, integrity, transactions) --- ## Discussion Questions 1. Why does the author say "microservices are not the goal"? What should the goal be? 2. What went wrong with Snap CI's early microservice adoption? What's the lesson? 3. Why might it be *easier* to decompose an existing monolith than to start greenfield with microservices? 4. When deciding what to split first, how do you balance "ease" vs. "benefit"? 5. Why is "code first" more common than "data first" when decomposing? When would you choose "data first"? 6. How does the strangler fig pattern work? Why is it attractive that you don't have to modify the monolith? 7. The chapter says database joins move from "data tier to application code tier." Why is this slower? 8. How do you handle data integrity without foreign key constraints across services? 9. What is the reporting database pattern solving? Why is it important to treat it as a microservice endpoint? 10. The author warns: "You won't appreciate the true horror, pain, and suffering that a microservice architecture can bring until you are running in production." What kinds of issues might emerge only in production? 11. When would you use a parallel run pattern? What does it give you that feature toggles don't? 12. The chapter mentions CodeScene for finding "hotspots." How would identifying volatile code help with microservice extraction?