이 프로젝트는 한 개발팀이 고민하고 있는 스택을 최대한 유사하게 구축한 상태에서 Severless Application의 권고안을 만들어 내기 위해서 작성된 앱이다.
- IaC : AWS CDK
- Serverless Stack : Lambda, API Gateway, DDB, Cognito
- Runtime & Framework : Node.js, NestJS
The cdk.json
file tells the CDK Toolkit how to execute your app.
npm run build
compile typescript to jsnpm run watch
watch for changes and compilenpm run test
perform the jest unit testscdk deploy
deploy this stack to your default AWS account/regioncdk diff
compare deployed stack with current statecdk synth
emits the synthesized CloudFormation template
이 앱은 Serverless App이 NoSQL과 SQL를 각각 Data Store로 사용할 때의 퍼포먼스를 비교하기 위해 준비된 App이다. Application이 DDB, RDS를 각각 사용할 경우를 테스트할 예정이다. 로컬에서는 아래 내용을 참고하여 SQL 서버를 연결할 수 있다.
-
/src/api/docker/docker-compose-mysql.yml을 사용하여 로컬 데이터 베이스 셋업
-
/src/api/.env 파일에서 MySQL의 접속 정보를 설정한 후 /src/api 위치에서 db 생성을 위한 migration 실행
npm run migration:generate -- -n CreateMysqlData
npm run migration:run
npm run start
- /src/api/docker/docker-compose-postgres.yml을 사용하여 로컬 데이터 베이스 셋업
- 상동
This sample app is mainly based on:
https://github.com/Sairyss/domain-driven-hexagon
- Domain-Driven Design (DDD)
- Hexagonal (Ports and Adapters) Architecture
- Secure by Design
- Clean Architecture
- Onion Architecture
- SOLID Principles
- Software Design Patterns
And many other sources (more links below in every chapter).
Before we begin, here are the PROS and CONS of using a complete architecture like this:
- Independent of external frameworks, technologies, databases, etc. Frameworks and external resources can be plugged/unplugged with much less effort.
- Easily testable and scalable.
- More secure. Some security principles are baked in design itself.
- The solution can be worked on and maintained by different teams, without stepping on each other's toes.
- Easier to add new features. As the system grows over time, the difficulty in adding new features remains constant and relatively small.
- If the solution is properly broken apart along bounded context lines, it becomes easy to convert pieces of it into microservices if needed.
-
This is a sophisticated architecture which requires a firm understanding of quality software principles, such as SOLID, Clean/Hexagonal Architecture, Domain-Driven Design, etc. Any team implementing such a solution will almost certainly require an expert to drive the solution and keep it from evolving the wrong way and accumulating technical debt.
-
Some of the practices presented here are not recommended for small-medium sized applications with not a lot of business logic. There is added up-front complexity to support all those building blocks and layers, boilerplate code, abstractions, data mapping etc. thus implementing a complete architecture like this is generally ill-suited to simple CRUD applications and could over-complicate such solutions. Some of the described below principles can be used in a smaller sized applications but must be implemented only after analyzing and understanding all pros and cons.
Diagram is mostly based on this one + others found online
In short, data flow looks like this (from left to right):
- Request/CLI command/event is sent to the controller using plain DTO;
- Controller parses this DTO, maps it to a Command/Query object format and passes it to a Application service;
- Application service handles this Command/Query; it executes business logic using domain services and/or entities and uses the infrastructure layer through ports;
- Infrastructure layer uses a mapper to convert data to format that it needs, uses repositories to fetch/persist data and adapters to send events or do other I/O communications, maps data back to domain format and returns it back to Application service;
- After application service finishes doing it's job, it returns data/confirmation back to Controllers;
- Controllers return data back to the user (if application has presenters/views, those are returned instead).
Each layer is in charge of it's own logic and has building blocks that usually should follow a Single-responsibility principle when possible and when it makes sense (for example, using Repositories
only for database access, using Entities
for business logic etc).
Keep in mind that different projects can have more or less steps/layers/building blocks than described here. Add more if application requires it, and skip some if application is not that complex and doesn't need all that abstraction.
General recommendation for any project: analyze how big/complex the application will be, find a compromise and use as many layers/building blocks as needed for the project and skip ones that may over-complicate things.
More in details on each step below.
This project's code examples use separation by modules (also called components). Each module's name should reflect an important concept from the Domain and have its own folder with a dedicated codebase, and each use case inside that module gets it's own folder to store most of the things it needs (this is also called Vertical Slicing).
It is easier to work on things that change together if those things are gathered relatively close to each other. Think of a module as a "box" that groups together related business logic.
Try not to create dependencies between modules or use cases, move shared logic into a separate files and make both depend on that instead of depending on each other.
Try to make every module independent and keep interactions between modules minimal. Think of each module as a mini application bounded by a single context. Consider module internals private and try to avoid direct imports between modules (like importing a class import SomeClass from '../SomeOtherModule'
) since this creates tight coupling, turns your code into a spaghetti and application into a big ball of mud.
To avoid coupling modules can communicate with each other by using a message bus, for example you can send commands using a commands bus or subscribe to events that other modules emit (more info on events and commands bus below).
This approach ensures loose coupling, and, if bounded contexts are defined and designed properly, each module can be easily separated into a microservice if needed without touching any domain logic.
Read more about modular programming benefits:
Each module is separated in layers described below.