{ "cells": [ { "cell_type": "markdown", "metadata": { "nbsphinx": "hidden" }, "source": [ "
\n", " `21 Microservices Architecture Patterns Study
\n", "\n", "

Chapter 0. KickOff

\n", "\n", "
\n", " Joseph Kim <cloudeyes@gmail.com>
\n", " Dec 10. 2020
\n", "
\n", "\n", "
\n", " \n", "
\n", "\n", "
\n", " Download Jupyter Notebook\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 00. Kick-Off\n", "\n", "## 얼음 깨기\n", "\n", "**[CyberMES 2027]**\n", "\n", "- 2027년의 MES 아키텍처는 어떻게 변할까요?\n", "- 사이버 펑크의 시대가 과연 올까요?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 내 손안의 마이크로서비스" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![](images/rpi-cluster.jpg)\n", "![](images/linkerd-01.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 스터디 소개\n", "\n", "**[내용]**cd\n", "\n", "- 스터디 모토\n", "- 스터디 미션\n", "- 스터디 규칙\n", "- 멤버 소개" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 스터디 모토\n", "\n", "- 무엇을 얻을것인지 생각\n", "- 자랑하고 인정받고 칭찬받는 놀이터\n", " - ~라는 최신 정보가 있어요 - 엄지척" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 스터디 미션\n", "\n", "- 메인 미션: 책 완독, 관심 내용 발표 1회이상\n", "- 그 이외에는 자유\n", "- 다양한 도전과제와 즐길거리 제공\n", " - 서브 퀘스트: MSA 토이 프로젝트 만들어서 호스팅하기\n", " - 사이드 퀘: AWS/Azure에서 MSA 서비스 호스팅, 다른 모던 언어 배워보기, DevOps 배우기, AWS 자격증, 라즈베리파이로 개인 클러스터 만들기, 모바일 앱 만들어 플레이스토에 배포하기 등\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 겁먹지 마세요\n", "\n", "- 책 내용이 어렵지만 다 이해할 필요 없어요.\n", " - 책을 \"완독\" 하는게 목적이지 \"완성\"하는게 목적이 아닙니다.\n", "- 메인 퀘스트가 어려우면 난이도를 낮추세요.\n", " - 챕터에서 알고싶은 범위를 한정하거나, 이해할 챕터 분량을 한정하기\n", "- 메인 퀘스트가 어려우면 서브/사이드 퀘스트에서 재미를 찾으세요!\n", "- 이해 안가는 내용은 슬랙 / 컨플루언스에 언제든 물어보세요." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 추천 미션 예:\n", "\n", "다음중 하나를 해보고 스샷을 공유해주세요, 추천 수에 따라서 스벅 쿠폰을 보내드립니다(?)\n", "\n", "- 마음에 드는 언어 하나 골라서 도메인 모델링 예제 구현 해보기\n", "- 파이썬 도커 개발 환경에 JupyterLab 셋팅해보기\n", "- 클라우드 플랫폼 (AWS, Azure, 또는 GCP) 가입해서 가상머신에 앱 하나 띄워보기\n", "- 책에서 감명깊게 본 부분 캡쳐해서 감상평 올리기~" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### 교재 선정 이유\n", "\n", "https://github.com/cosmicpython/book\n", "\n", "- 거의 유일 무이한 파이썬 기반 아키텍처 북\n", "- 실천적인 접근 방법: 이론만큼 구현에도 중점\n", "- 최신 교재 / 최신 파이썬 기술 포함\n", "- MSA를 맹신하지 않음.\n", "- 엄청난 아마존 평점" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 스터디 규칙\n", "\n", "- 공휴일 / 연휴 제외 매주 모이는 것을 원칙으로 합니다.\n", "- 매주 내드리는 메인 미션은 반드시 완수하셔야 합니다.\n", "- 메인 미션은. 정해진 분량의 교재 범위를 읽어오시고\n", "- 지난번 모임에서 읽은 챕터의 테스트 코드를 완성시켜오시는 것입니다.\n", "- 스터디를 마치기 전까지 최소 1번 이상 발표를 하셔야 합니다" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 토론\n", "\n", "**[내용]**\n", "\n", "- 왜 마이크로서비스가 대세일까요?\n", "- 왜 파이썬으로 하나요?\n", "- 스터디 운영방법?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 왜 마이크로서비스가 대세일까요?\n", "\n", "- 마이크로서비스가 소프트웨어 복잡성을 해결하는 현재까지 가장 진보된 방법이기 때문에\n", "- 거대한 모놀리식 서비스를 세부레벨로 찢어서 부서간의 사일로 해결\n", "- 그럼에도 개별 서비스들의 영향의 컨테이너 레벨로 안전하게 격리하고 서비스 메시를 통해 스케일 아웃 가능\n", "- SOA처럼 헤비한 프레임워크를필요로 하지 않아 간단하게 시작해볼 수 있음\n", "- 마이크로서비스 운영 기술을 통해 엔터프라이즈 레벨로 확장이 용이" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 왜 파이썬으로 하나요?\n", "\n", "- 가장 인기있는 언어\n", "- 가장 빨리 발전하고 있는 언어 중 하나\n", "- 오너십과 파이썬 커뮤니티니의 건전성\n", " - 리눅스와 비슷? PEP 읽어보면 귀도 반 로섬이 거의 항상 참여" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 효과적인 스터디 운영방법은?\n", "\n", "- 무엇을 공부하게 되나요?\n", "- 너무 어렵지는 않을까요?\n", "- 재미있게 열심히 할 수 있을까요?\n", "- 회비 기반으로 동기부여하기\n", " - 총무가 필요합니다. (매 주 5000원씩 회비를 걷습니다)\n", " - 엄지척을 많이 받은 발표자에게 상품 쿠폰을 제공합니다." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 책 둘러보기\n", "\n", "- https://github.com/cosmicpython/book\n", "\n", "> Whenever we introduce an architectural pattern in this book, we’ll always ask,\n", ">\n", "> “What do we get for this? And what does it cost us?”\n", "\n", "### Introduction\n", "\n", "- Why Do Our Designs Go Wrong?\n", "- Encapsulation and Abstractions\n", "- Layering\n", "- The Dependency Inversion Principle\n", "- A Place for All Our Business Logic: The Domain Model\n", "\n", "### I. Building an Architecture to Support Domain Modeling\n", "\n", "#### 1. Domain Modeling\n", "\n", "- What Is a Domain Model?\n", "- Exploring the Domain Language\n", "- Unit Testing Domain Models\n", "- Not Everything Has to Be an Object: A Domain Service Function\n", "\n", "#### 2. Repository Pattern\n", "\n", "- Persisting Our Domain Model\n", "- Some Pseudocode: What Are We Going to Need?\n", "- Applying the DIP to Data Access\n", "- Reminder: Our Model\n", "- Introducing the Repository Pattern\n", "- Building a Fake Repository for Tests Is Now Trivial!\n", "- What Is a Port and What Is an Adapter, in Python?\n", "\n", "#### 3. A Brief Interlude: On Coupling and Abstractions\n", "\n", "- Abstracting State Aids Testability\n", "- Choosing the Right Abstraction(s)\n", "- Implementing Our Chosen Abstractions\n", "\n", "#### 4. Our First Use Case: Flask API and Service Layer\n", "\n", "- Connecting Our Application to the Real World\n", "- A First End-to-End Test\n", "- The Straightforward Implementation\n", "- Error Conditions That Require Database Checks\n", "- Introducing a Service Layer, and Using FakeRepository to Unit Test It\n", "- Why Is Everything Called a Service?\n", "- Putting Things in Folders to See Where It All Belongs\n", "\n", "#### 5. TDD in High Gear and Low Gear\n", "\n", "- How Is Our Test Pyramid Looking?\n", "- Should Domain Layer Tests Move to the Service Layer?\n", "- On Deciding What Kind of Tests to Write\n", "- High and Low Gear\n", "- Fully Decoupling the Service-Layer Tests from the Domain\n", "- Carrying the Improvement Through to the E2E Tests\n", "\n", "#### 6. Unit of Work Pattern\n", "\n", "- The Unit of Work Collaborates with the Repository\n", "- Test-Driving a UoW with Integration Tests\n", "- Unit of Work and Its Context Manager\n", "- Using the UoW in the Service Layer\n", "- Explicit Tests for Commit/Rollback Behavior\n", "- Explicit Versus Implicit Commits\n", "- Examples: Using UoW to Group Multiple Operations into an Atomic Unit\n", "- Tidying Up the Integration Tests\n", "\n", "#### 7. Aggregates and Consistency Boundaries\n", "\n", "- Why Not Just Run Everything in a Spreadsheet?\n", "- Invariants, Constraints, and Consistency\n", "- What Is an Aggregate?\n", "- Choosing an Aggregate\n", "- One Aggregate = One Repository\n", "- What About Performance?\n", "- Optimistic Concurrency with Version Numbers\n", "- Testing for Our Data Integrity Rules\n", "\n", "### II. Event-Driven Architecture\n", "\n", "#### 8. Events and the Message Bus\n", "\n", "- Avoiding Making a Mess\n", "- Single Responsibility Principle\n", "- All Aboard the Message Bus!\n", "- Option 1: The Service Layer Takes Events from the Model and Puts Them on the Message Bus\n", "- Option 2: The Service Layer Raises Its Own Events\n", "- Option 3: The UoW Publishes Events to the Message Bus\n", "\n", "#### 9. Going to Town on the Message Bus\n", "\n", "- A New Requirement Leads Us to a New Architecture\n", "- Refactoring Service Functions to Message Handlers\n", "- Implementing Our New Requirement\n", "- Test-Driving a New Handler\n", "- Optionally: Unit Testing Event Handlers in Isolation with a Fake Message Bus\n", "\n", "#### 10. Commands and Command Handler\n", "\n", "- Commands and Events\n", "- Differences in Exception Handling\n", "- Discussion: Events, Commands, and Error Handling\n", "- Recovering from Errors Synchronously\n", "\n", "#### 11. Event-Driven Architecture: Using Events to Integrate Microservices\n", "\n", "- Distributed Ball of Mud, and Thinking in Nouns\n", "- Error Handling in Distributed Systems\n", "- The Alternative: Temporal Decoupling Using Asynchronous Messaging\n", "- Using a Redis Pub/Sub Channel for Integration\n", "- Test-Driving It All Using an End-to-End Test\n", "- Internal Versus External Events\n", "\n", "#### 12. Command-Query Responsibility Segregation (CQRS)\n", "\n", "- Domain Models Are for Writing\n", "- Most Users Aren’t Going to Buy Your Furniture\n", "- Post/Redirect/Get and CQS\n", "- Hold On to Your Lunch, Folks\n", "- Testing CQRS Views\n", "- “Obvious” Alternative 1: Using the Existing Repository\n", "- Your Domain Model Is Not Optimized for Read Operations\n", "- “Obvious” Alternative 2: Using the ORM\n", "- SELECT N+1 and Other Performance Considerations\n", "- Time to Completely Jump the Shark\n", "- Changing Our Read Model Implementation Is Easy\n", "\n", "#### 13. Dependency Injection (and Bootstrapping)\n", "\n", "- Implicit Versus Explicit Dependencies\n", "- Aren’t Explicit Dependencies Totally Weird and Java-y?\n", "- Preparing Handlers: Manual DI with Closures and Partials\n", "- An Alternative Using Classes\n", "- A Bootstrap Script\n", "- Message Bus Is Given Handlers at Runtime\n", "- Using Bootstrap in Our Entrypoints\n", "- Initializing DI in Our Tests\n", "- Building an Adapter “Properly”: A Worked Example\n", "\n", "#### Epilogue\n", "\n", "- What Now?\n", "- How Do I Get There from Here?\n", "- Separating Entangled Responsibilities\n", "- Identifying Aggregates and Bounded Contexts\n", "- An Event-Driven Approach to Go to Microservices via Strangler Pattern\n", "- Convincing Your Stakeholders to Try Something New\n", "- Questions Our Tech Reviewers Asked That We Couldn’t Work into Prose\n", "- Footguns\n", "- More Required Reading\n", "\n", "#### Appendicies\n", "\n", "- A. Summary Diagram and Table\n", "- B. A Template Project Structure\n", "- C. Swapping Out the Infrastructure: Do Everything with CSVs\n", "- D. Repository and Unit of Work Patterns with Django\n", "- E. Validation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 참고: 프로그래밍 언어 랭킹\n", "\n", "#### PYPL 2020\n", "\n", "- https://pypl.github.io/PYPL.html\n", "\n", "1. **Python** (30.34% / **+1.2**%) -- *더 할말이?*\n", "2. Java (17.23% / -1.7%)\n", "3. JavaScript (8.65% / +0.6%) -- *[**TypeScript**]: 왕위를 계승중입니다...*\n", "4. **C#** (6.44% / -0.8%) -- *.NET Framwork의 최강자*\n", "5. C/C++ / 6.11% / +0.1%)\n", "6. PHP (5.88% / -0.3%)\n", "7. R (3.84% / +0.1%) -- *한때 유일하게 쓸만한 통계/데이터 과학 언어, [Jupyter의 3대장](https://blog.jupyter.org/i-python-you-r-we-julia-baf064ca1fb6?gi=186c617bcf06)*\n", "8. Objective-C (3.75% / +1.2%)\n", "9. Swift (2.17% / -0.3%)\n", "10. Matlab (1.77% / -0.0%)\n", "11. **TypeScript** (1.62% / -0.2%)\n", "12. **Go** (1.52% / +0.3%) -- *마이크로서비스 트렌드의 대세*\n", "13. **Kotlin** (1.44% / -0.2%) -- *JVM 기반 언어중 최고 대세*\n", "14. Ruby (1.28% / -0.1%)\n", "15. **Rust** (1.12% / +0.5%) -- *마이크로서비스 트렌드의 또다른 대세언어, 함수형 언어의 적자*\n", "16. VBA (1.05% / -0.3%)\n", "17. Scala (0.97% / -0.1%) -- *Kotlin에게 인기 빼앗기는 중...*\n", "18. Visual Basic (0.67% / -0.3%)\n", "19. Ada (0.61% / +0.3%)\n", "20. Dart (0.58% / +0.2%) -- *Flutter 프레임웍으로 인기 급상승*\n", "21. Lua (0.55% / +0.2%)\n", "22. Perl (0.46% / -0.1%)\n", "23. **Groovy** (0.43% / -0.0%) -- *최고 Cool한 JVM 언어였지만.. Kotlin/Scala에 인기 다뺏김 Gradle로 연명중*\n", "24. Abap (0.41% / -0.1%)\n", "25. **Julia** (0.33% / +0.1%) -- *Data Scientist 들에게 사랑받는 언어, [Jupyter의 3대장](https://blog.jupyter.org/i-python-you-r-we-julia-baf064ca1fb6?gi=186c617bcf06)*\n", "26. **Haskell** (0.27% / -0.0%)\n", "27. Cobol (0.26% / -0.1%)\n", "28. Delphi (0.26% / +0.0%)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3분 DDD" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 도메인 모델링이란\n", "\n", "#### Domain modeling\n", "\n", "- The part of your code that is closest to the business.\n", "- The most likely to change, and the place where you deliver the most value to the business.\n", "- Need to make it easy to understand and modify.\n", "\n", "#### Distinguish entities from value objects\n", "\n", "**Value Objects**\n", "- Defined by its attributes. \n", "- Usually best implemented as an immutable type. \n", " - If you change an attribute on a Value Object, it represents a different object. \n", " \n", "**Entities**\n", "- Even though an entity has attributes that may vary over time, it will still be the same entity. \n", "- Important to define what does uniquely identify an entity \n", " - usually some sort of name or reference field).\n", "\n", "#### Not everything has to be an object\n", "\n", "- The \"verbs\" in your code be functions. \n", " - e.g) `FooManager, BarBuilder, BazFactory`\n", " - vs.\n", " - `manage_foo(), build_bar(), get_baz()`\n", "\n", "#### This is the time to apply your best OO design principles\n", "\n", "- Revisit the SOLID principles\n", "- All the other good heuristics like \"has a versus is-a,\" \"prefer composition over inheritance,\" and so on.\n", "\n", "#### You’ll also want to think about consistency boundaries and aggregates\n", "But that’s a topic for [chapter_07_aggregate].\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Value Object in Python" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from dataclasses import dataclass\n", "\n", "@dataclass(frozen=True)\n", "class User:\n", " name: str\n", " age: int\n", " \n", "user1 = User('John', 16)\n", "user2 = User(age=16, name='John')\n", "\n", "user1 == user2 # True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 참고: 모던 프로그래밍 언어에서 Value Object 구현 방법" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### C# 9 (.NET 5.0)\n", "\n", "```c#\n", "public record User(String Name, int Age);\n", "\n", "var user1 = new User(\"John\", 16);\n", "var user2 = new User(\"John\", 16);\n", "user1 == user2\n", "\n", "public record User2 {\n", " public String Name { get; init; }\n", " public int Age { get; init; }\n", "};\n", "\n", "var user1 = new User2 { Name=\"Love\", Age=17 };\n", "var user2 = new User2 { Age=17, Name=\"Love\" };\n", "user1 == user2\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### TypeScript\n", "\n", "```typescript\n", "// `ValueObject` 구현 방법은 별도 노트북을 참조하세요.\n", "interface UserProps {\n", " name: String\n", " age: number\n", "}\n", "\n", "class User extends ValueObject {\n", " constructor(props) { super(props) }\n", "}\n", "\n", "const user1 = new User({name: 'John', age: 16})\n", "const user2 = new User({age: 16, name: 'John'})\n", "\n", "user1 === user2 // false\n", "user1.equals(user2) // true\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Go\n", "```go\n", "type User struct { \n", " name string\n", " age int\n", "}\n", "\n", "user1 := User { \"John\", 16 }\n", "user2 := User { \"John\", 16 }\n", "\n", "user1 == user2 // true\n", "\n", "// 당황: Go는 Immuable 속성을 지원하지 않아서 동적으로 속성을 변경 가능.\n", "\n", "user1.name = \"Test\"\n", "user1 == user2 // false\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Kotlin\n", "\n", "```kotlin\n", "data class User(val name: String, val age: Int)\n", "\n", "val user1 = User(\"John\", 10)\n", "val user2 = User(\"John\", 10)\n", "\n", "user1 == user2 // true\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Rust\n", "\n", "```rust\n", "##[derive(PartialEq, Eq)]\n", "struct User { name: String, age: u16 }\n", "\n", "let user1 = User { name: String::from(\"John\"), age: 16 };\n", "let user2 = User { name: String::from(\"John\"), age: 16 };\n", "\n", "user1 == user2 // true\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Groovy\n", "\n", "```groovy\n", "import groovy.transform.Immutable\n", "\n", "@Immutable class User {\n", " String name\n", " int age\n", "}\n", "\n", "final user1 = new User('John', 16)\n", "final user2 = new User([age: 16, name: 'John'])\n", "\n", "user1 == user2 // true\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Julia\n", "\n", "```julia\n", "import Base.@kwdef\n", "\n", "@kwdef struct User\n", " name::String\n", " age::UInt8\n", "end\n", "\n", "user1 = User(\"John\", 16)\n", "user2 = User(age=16, name=\"John\")\n", "\n", "user1 == user2 # true\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "##### Haskell\n", "\n", "```haskell\n", "data User = User \n", " { name :: String, \n", " age :: Int \n", " } deriving (Eq, Show)\n", " \n", "user1 = User { name = \"John\", age = 16 }\n", "user2 = User { age = 16, name = \"John\" }\n", "user1 == user2 -- True\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 결론\n", "\n", "- 3개월 안에 책을 다 읽을 겁니다\n", "- 3개월 동안 MSA 토이 프로젝트를 완성하고 발표를 하게 될 거에요.\n", "- 스터디는 정말 재밌게 할 수 있을 겁니다!\n", "- 특별한 일 없으면 매 주 오프라인/온라인 교대로 만날거에요.\n", "- 다음주에 또 만나요 :D" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.1" }, "toc-autonumbering": false, "toc-showcode": false, "toc-showtags": false, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }