원티드 프리온보딩 챌린지 백엔드 31차
본 프로젝트의 API 명세서는 GitHub Pages을 통해 Swagger UI로 제공됩니다.
API 명세서 바로가기 |
-
GitHub Pages에 게시된 Swagger 문서는 정적 문서용으로 제공되며,
백엔드 서버 및 데이터베이스가 연결되어 있지 않기 때문에 실제 요청은 처리되지 않습니다. -
API 요청을 정상적으로 테스트하려면,
로컬 환경에서 Docker Compose를 사용해 서버와 데이터베이스를 실행한 후 Swagger UI에 접속합니다.
Category | Method | URI | Summary |
---|---|---|---|
상품 관리 | POST | /products | 상품 등록 |
GET | /products | 상품 목록 조회 | |
GET | /products/{id} | 상품 상세 조회 | |
PUT | /products/{id} | 상품 수정 | |
DELETE | /products/{id} | 상품 삭제 | |
상품 옵션 관리 | POST | /products/{id}/options | 상품 옵션 추가 |
PUT | /products/{id}/options/{optionId} | 상품 옵션 수정 | |
DELETE | /products/{id}/options/{optionId} | 상품 옵션 삭제 | |
POST | /products/{id}/images | 상품 이미지 추가 | |
카테고리 | GET | /categories | 카테고리 목록 조회 |
GET | /categories/{id}/products/ | 특정 카테고리의 상품 목록 조회 | |
메인 페이지 | GET | /main | 메인 페이지 상품 및 카테고리 목록 조회 |
리뷰 | GET | /products/{id}/reviews | 상품 리뷰 조회 |
POST | /products/{id}/reviews | 상품 리뷰 작성 | |
PUT | /reviews/{id} | 리뷰 수정 | |
DELETE | /reviews/{id} | 리뷰 삭제 |
graph TD
subgraph "API Layer"
api[API Server]
redis[(Redis)]
end
subgraph "Command Side"
postgres[(PostgreSQL)]
end
subgraph "Streaming Layer"
cdc@{ shape: rounded, label: Debezium }
kafka@{ shape: das, label: "**Kafka**" }
ksql@{ shape: trap-t, label: "ksqlDB" }
sink@{ shape: sm-circ, label: "SinkConnector" }
end
subgraph "Query Side"
mongo[(MongoDB)]
elasticsearch[( ElasticSearch)]
end
%% Cache Layer
api <-->|⚡ Caching| redis
%% Command flow
api --->|📥 Command| postgres
%% CDC Flow
postgres e1@-.->|📡 WAL Log | cdc
cdc e2@-->|📣 Change Event| kafka
%% Projection flow
ksql e3@<-.->|✉️ Topic| kafka
%% Sink flow
kafka e4@-.-> sink
sink e5@-.->|📝 Document| mongo
sink e6@-.->|🔍 Index| elasticsearch
%% Query flow
api -->|"📤 Query (Lookup)"| mongo
%% Query flow
api -->|"📤 Query (Search)"| elasticsearch
e1@{ animation: fast }
e2@{ animation: fast }
e3@{ animation: fast }
e4@{ animation: fast }
e5@{ animation: fast }
e6@{ animation: fast }
click postgres "https://github.com/NarciSource/Pre-Onboarding-Challenge-BE-31/tree/main/db/rdb"
click cdc "https://github.com/NarciSource/Pre-Onboarding-Challenge-BE-31/tree/main/config/connectors/source"
click sink "https://github.com/NarciSource/Pre-Onboarding-Challenge-BE-31/tree/main/config/connectors/sink"
click ksql "https://github.com/NarciSource/Pre-Onboarding-Challenge-BE-31/tree/main/db/ksqldb"
click elasticsearch "https://github.com/NarciSource/Pre-Onboarding-Challenge-BE-31/tree/main/config/elasticsearch/templates"
-
API Layer
-
API Server: 클라이언트로부터 Command(쓰기 요청)와 Query(읽기 요청)를 처리하는 진입점.
Command 요청은 Command Side를 통해 처리하고, Query 요청은 Query Side를 통해 처리. -
Redis: API 레이어에서 읽기 요청의 응답 속도를 높이기 위해 캐시로 사용.
API 서버와 양방향으로 연결되어 캐시 조회 및 갱신 수행.
-
-
Command Side
- PostgreSQL: 시스템의 쓰기 전용 데이터 저장소.
Command Side의 영속 데이터가 저장되며, 변경 사항은 CDC로 감지됨.
- PostgreSQL: 시스템의 쓰기 전용 데이터 저장소.
-
CDC (Change Data Capture)
- Debezium (PostgreSQL Connector): PostgreSQL의 WAL(Write-Ahead Log)을 읽어 변경 이벤트를 추출.
추출된 이벤트를 Kafka 토픽으로 발행.
- Debezium (PostgreSQL Connector): PostgreSQL의 WAL(Write-Ahead Log)을 읽어 변경 이벤트를 추출.
-
Messaging
- Kafka: 모든 변경 이벤트와 스트리밍 데이터를 전달하는 중앙 메시징 플랫폼.
Debezium에서 발행한 CDC 이벤트를 토픽에 저장하고, 이를 소비자(ksqlDB, Sink Connector 등)가 구독하도록 함.
- Kafka: 모든 변경 이벤트와 스트리밍 데이터를 전달하는 중앙 메시징 플랫폼.
-
Streaming Processing
- ksqlDB: Kafka 토픽의 실시간 스트림을 읽어 변환, 필터링, 집계 등 스트리밍 처리 수행.
처리된 결과를 새로운 Kafka 토픽에 발행하여 후속 시스템에서 사용 가능하게 함.
- ksqlDB: Kafka 토픽의 실시간 스트림을 읽어 변환, 필터링, 집계 등 스트리밍 처리 수행.
-
Query Side
-
MongoDB: CQRS에서의 읽기 전용 Document DB 역할.
Kafka Connect MongoDB Sink Connector가 Kafka의 처리된 데이터를 저장.
API Layer의 ID 조회나 집계 기반 읽기 요청 처리. -
Elasticsearch: CQRS에서의 읽기 전용 Search Index 역할.
Kafka Connect Elasticsearch Sink Connector가 Kafka의 처리된 데이터를 색인화.
API Layer의 검색 및 전문(Full-text) 조회 요청 처리.
-
-
데이터 흐름 요약
-
Command:
API Server → PostgreSQL → Debezium → Kafka → ksqlDB → Kafka → Sink Connectors → MongoDB / Elasticsearch
-
Query:
API Server → Redis (캐시) / MongoDB / Elasticsearch
-
Module Dependency Diagram
graph
subgraph DockerNetwork["shared-net"]
direction RL
subgraph Event-Streaming
zookeeper@{ shape: dbl-circ }
kafka@{ shape: das, label: "**Kafka**" }
kafka-ui@{ shape: win-pane }
ksqldb-server@{ shape: trap-t }
schema-registry@{ shape: div-rect }
debezium@{ shape: diamond }
connector-init@{ shape: odd }
ksql-init@{ shape: odd }
end
subgraph Application
server@{ shape: rect }
rds@{ shape: cyl }
mongo@{ shape: cyl }
mongo-init@{ shape: odd }
elasticsearch@{ shape: cyl }
elasticsearch-init@{ shape: odd }
redis@{ shape: cyl }
kibana@{ shape: win-pane }
end
end
%% depends_on 관계
server -..->|🩺 healthcheck| rds & redis
mongo-init -.-> mongo
server -..->|🩺| mongo
server -..->|🩺| elasticsearch
elasticsearch-init -.->|🩺| elasticsearch
kibana --> elasticsearch
server -...-> kafka
connector-init -.->|🩺| debezium --> kafka
debezium --->|🩺| schema-registry
ksql-init -.->|🩺| ksqldb-server --> kafka
ksql-init -.-> connector-init
kafka-ui --> kafka --> zookeeper
erDiagram
products {
BIGINT id PK
VARCHAR name
VARCHAR slug
VARCHAR short_description
TEXT full_description
TIMESTAMP created_at
TIMESTAMP updated_at
BIGINT seller_id FK
BIGINT brand_id FK
VARCHAR status
}
categories {
BIGINT id PK
VARCHAR name
VARCHAR slug
TEXT description
BIGINT parent_id FK
INTEGER level
VARCHAR image_url
}
sellers {
BIGINT id PK
VARCHAR name
TEXT description
VARCHAR logo_url
DECIMAL rating
VARCHAR contact_email
VARCHAR contact_phone
TIMESTAMP created_at
}
brands {
BIGINT id PK
VARCHAR name
VARCHAR slug
TEXT description
VARCHAR logo_url
VARCHAR website
}
product_details {
BIGINT id PK
BIGINT product_id FK
DECIMAL weight
JSONB dimensions
TEXT materials
VARCHAR country_of_origin
TEXT warranty_info
TEXT care_instructions
JSONB additional_info
}
product_prices {
BIGINT id PK
BIGINT product_id FK
DECIMAL base_price
DECIMAL sale_price
DECIMAL cost_price
VARCHAR currency
DECIMAL tax_rate
}
product_categories {
BIGINT id PK
BIGINT product_id FK
BIGINT category_id FK
BOOLEAN is_primary
}
product_option_groups {
BIGINT id PK
BIGINT product_id FK
VARCHAR name
INTEGER display_order
}
product_options {
BIGINT id PK
BIGINT option_group_id FK
VARCHAR name
DECIMAL additional_price
VARCHAR sku
INTEGER stock
INTEGER display_order
}
product_images {
BIGINT id PK
BIGINT product_id FK
VARCHAR url
VARCHAR alt_text
BOOLEAN is_primary
INTEGER display_order
BIGINT option_id FK
}
product_tags {
BIGINT id PK
BIGINT product_id FK
BIGINT tag_id FK
}
tags {
BIGINT id PK
VARCHAR name
VARCHAR slug
}
reviews {
BIGINT id PK
BIGINT product_id FK
BIGINT user_id FK
INTEGER rating
VARCHAR title
TEXT content
TIMESTAMP created_at
TIMESTAMP updated_at
BOOLEAN verified_purchase
INTEGER helpful_votes
}
users {
BIGINT id PK
VARCHAR name
VARCHAR email
VARCHAR avatar_url
TIMESTAMP created_at
}
products }o--|| sellers : seller
products }o--|| brands : brand
product_details ||--|| products : product
product_prices ||--|| products : product
product_categories }o--|| products : product
product_categories }o--|| categories : category
categories ||--o{ categories : parent
product_option_groups }o--|| products : product
product_options }o--|| product_option_groups : option_group
product_images }o--o| product_options : option
product_images }o--|| products : product
product_tags }o--|| products : product
product_tags }o--|| tags : tag
reviews }o--|| products : product
reviews }o--o| users : user
테스트 통과 여부와 커버리지 현황은 시각적으로 제공됩니다.
테스트 리포트 바로가기 | 커버리지 대시보드 바로가기 |
열기
Pre-Onboarding-Challenge-BE-31
├─ .env
├─ README.md
├─ docker-compose.yml
│ ├─ docker-compose.streaming.yml
│ ├─ docker-compose.tools.yml
│ ├─ Dockerfile.server
│ └─ Dockerfile.cdc
├─ jest.config.ts
│ ├─ jest.base-config.ts
│ ├─ jest.global-setup.ts
│ └─ jest.teardown.ts
├─ package.json
│ ├─ package-lock.json
│ ├─ .prettierrc
│ ├─ eslint.config.mjs
│ └─ nest-cli.json
├─ tsconfig.json
├─ config
│ ├─ logging
│ │ └─ log4j.properties
│ ├─ connectors
│ │ ├─ register.sh
│ │ ├─ source
│ │ │ ├─ postgres-product-connector.json
│ │ │ ├─ postgres-product_option-connector.json
│ │ │ ├─ postgres-product_related-connector.json
│ │ │ ├─ postgres-merchant-connector.json
│ │ │ ├─ postgres-category-connector.json
│ │ │ ├─ postgres-review-connector.json
│ │ │ └─ postgres-tag-connector.json
│ │ └─ sink
│ │ ├─ mongo-product_summary-connector.json
│ │ ├─ mongo-product_catalog-connector.json
│ │ ├─ mongo-featured_category-connector.json
│ │ ├─ mongo-nested_category-connector.json
│ │ ├─ es-product_summary-connector.json
│ │ ├─ es-product_catalog-connector.json
│ │ ├─ es-featured_category-connector.json
│ │ └─ es-nested_category-connector.json
│ ├─ mongo
│ │ └─ init-replica.js
│ └─ elasticsearch
│ ├─ register.sh
│ └─ templates
│ ├─ product_summary-template.json
│ ├─ product_catalog-template.json
│ ├─ featured_category-template.json
│ └─ nested_category-template.json
├─ db
│ ├─ rdb
│ │ ├─ 01.ddl.sql
│ │ ├─ 02.sellers.sql
│ │ ├─ 03.brands.sql
│ │ ├─ 04.categories.sql
│ │ ├─ 05.tags.sql
│ │ ├─ 06.products.sql
│ │ ├─ 07.product_options.sql
│ │ ├─ 08.product_extended.sql
│ │ ├─ 09.users.sql
│ │ └─ 10.reviews.sql
│ └─ ksqldb
│ ├─ 01.raw.sql
│ ├─ 02.state_product_category.sql
│ ├─ 03.state_category_product.sql
│ ├─ 04.state_image.sql
│ ├─ 05.state_rating.sql
│ ├─ 06.state_option.sql
│ ├─ 07.view_product_summary.sql
│ ├─ 08.view_product_catalog.sql
│ ├─ 09.view_featured_category.sql
│ └─ 10.view_nested_category.sql
├─ libs
│ ├─ config
│ │ └─ src
│ │ ├─ index.ts
│ │ ├─ typeorm.config.ts
│ │ ├─ mongo.config.ts
│ │ ├─ elasticsearch.config.ts
│ │ └─ redis.config.ts
│ ├─ auth
│ │ └─ src
│ │ ├─ jwtInterceptor.ts
│ │ └─ verifier.ts
│ ├─ domain
│ │ ├─ tsconfig.lib.json
│ │ └─ src
│ │ ├─ entities
│ │ │ ├─ index.ts
│ │ │ ├─ Product.ts
│ │ │ ├─ Product_Category.ts
│ │ │ ├─ Product_Detail.ts
│ │ │ ├─ Product_Image.ts
│ │ │ ├─ Product_Option.ts
│ │ │ ├─ Product_Option_Group.ts
│ │ │ ├─ Product_Price.ts
│ │ │ ├─ Product_Tag.ts
│ │ │ ├─ Brand.ts
│ │ │ ├─ Seller.ts
│ │ │ ├─ Category.ts
│ │ │ ├─ Review.ts
│ │ │ ├─ User.ts
│ │ │ ├─ Tag.ts
│ │ │ ├─ Product_Summary.ts
│ │ │ ├─ Product_Catalog.ts
│ │ │ ├─ Featured_Category.ts
│ │ │ └─ Nested_Category.ts
│ │ └─ repository
│ │ ├─ index.ts
│ │ ├─ IBaseRepository.ts
│ │ ├─ IQueryRepository.ts
│ │ ├─ IViewRepository.ts
│ │ └─ ISearchRepository.ts
│ └─ infrastructure
│ ├─ rdb
│ │ ├─ tsconfig.lib.json
│ │ └─ src
│ │ ├─ module.ts
│ │ ├─ entities
│ │ │ ├─ index.ts
│ │ │ ├─ Product.entity.ts
│ │ │ │ └─ Product.entity.test.ts
│ │ │ ├─ Product_Category.entity.ts
│ │ │ │ └─ Product_Category.entity.test.ts
│ │ │ ├─ Product_Detail.entity.ts
│ │ │ │ └─ Product_Detail.entity.test.ts
│ │ │ ├─ Product_Image.entity.ts
│ │ │ │ └─ Product_Image.entity.test.ts
│ │ │ ├─ Product_Option.entity.ts
│ │ │ │ └─ Product_Option.entity.test.ts
│ │ │ ├─ Product_Option_Group.entity.ts
│ │ │ │ └─ Product_Option_Group.entity.test.ts
│ │ │ ├─ Product_Price.entity.ts
│ │ │ │ └─ Product_Price.entity.test.ts
│ │ │ ├─ Product_Tag.entity.ts
│ │ │ │ └─ Product_Tag.entity.test.ts
│ │ │ ├─ Brand.entity.ts
│ │ │ │ └─ Brand.entity.test.ts
│ │ │ ├─ Seller.entity.ts
│ │ │ │ └─ Seller.entity.test.ts
│ │ │ ├─ Category.entity.ts
│ │ │ │ └─ Category.entity.test.ts
│ │ │ ├─ Review.entity.ts
│ │ │ │ └─ Review.entity.test.ts
│ │ │ ├─ User.entity.ts
│ │ │ │ └─ User.entity.test.ts
│ │ │ └─ Tag.entity.ts
│ │ │ └─ Tag.entity.test.ts
│ │ └─ repositories
│ │ ├─ index.ts
│ │ ├─ base.repository.mixin.ts
│ │ ├─ createRepositoryProvider.ts
│ │ └─ provider.ts
│ ├─ mongo
│ │ ├─ tsconfig.lib.json
│ │ └─ src
│ │ ├─ module.ts
│ │ ├─ models
│ │ │ ├─ sub
│ │ │ │ ├─ Brand.model.ts
│ │ │ │ ├─ Seller.model.ts
│ │ │ │ ├─ Category.model.ts
│ │ │ │ ├─ Detail.model.ts
│ │ │ │ ├─ Image.model.ts
│ │ │ │ ├─ Option.model.ts
│ │ │ │ ├─ OptionGroup.model.ts
│ │ │ │ ├─ Price.model.ts
│ │ │ │ ├─ Rating.model.ts
│ │ │ │ └─ Tag.model.ts
│ │ │ ├─ index.ts
│ │ │ ├─ ProductSummary.model.ts
│ │ │ ├─ ProductCatalog.model.ts
│ │ │ ├─ FeaturedCategory.model.ts
│ │ │ ├─ NestedCategory.model.ts
│ │ │ └─ provider.ts
│ │ └─ repositories
│ │ ├─ index.ts
│ │ ├─ createQueryRepositoryProvider.ts
│ │ ├─ Query.repository.ts
│ │ │ └─ Query.repository.test.ts
│ │ └─ provider.ts
│ └─ es
│ ├─ tsconfig.lib.json
│ └─ src
│ ├─ module.ts
│ ├─ libs
│ │ └─ decorator.ts
│ ├─ mapping
│ │ ├─ index.ts
│ │ └─ Summary.mapping.ts
│ └─ repositories
│ └─ Search.repository.ts
└─ apps
└─ api-server
├─ jest.config.ts
├─ tsconfig.json
│ └─ tsconfig.build.json
└─ src
├─ main.ts
│ └─ module.swagger.ts
│ └─ module.ts
├─ __mocks__
│ └─ entityManagerMock.ts
├─ __test-utils__
│ ├─ getValidateDTO.ts
│ └─ test-module.ts
├─ utility
│ ├─ downloadOpenAPI.ts
│ ├─ extractDTOExample.ts
│ └─ generatorSwagger.ts
├─ libs
│ ├─ constants
│ │ └─ ErrorCode.ts
│ ├─ decorators
│ │ ├─ index.ts
│ │ ├─ ApiErrorResponse.ts
│ │ ├─ ApiStandardResponse.ts
│ │ ├─ ResponseType.ts
│ │ └─ Transform.ts
│ ├─ filters
│ │ ├─ index.ts
│ │ ├─ BadRequestExceptionFilter.ts
│ │ ├─ ConflictExceptionFilter.ts
│ │ ├─ ForbiddenExceptionFilter.ts
│ │ ├─ InternalServerErrorExceptionFilter.ts
│ │ ├─ NotFoundExceptionFilter.ts
│ │ ├─ QueryFailedExceptionFilter.ts
│ │ └─ UnauthorizedExceptionFilter.ts
│ └─ interceptors
│ └─ ResponseInterceptor.ts
│ └─ ResponseInterceptor.test.ts
├─ shared
│ ├─ dto
│ │ ├─ index.ts
│ │ ├─ Error.dto.ts
│ │ │ └─ Error.dto.test.ts
│ │ ├─ Filter.dto.ts
│ │ ├─ PaginationSummary.dto.ts
│ │ │ └─ PaginationSummary.dto.test.ts
│ │ ├─ Param.dto.ts
│ │ │ └─ Param.dto.test.ts
│ │ └─ Response.dto.ts
│ │ └─ Response.dto.test.ts
│ └─ mappers
│ ├─ index.ts
│ └─ to_FilterDTO.ts
├─ browsing
│ ├─ module.ts
│ ├─ application
│ │ └─ query
│ │ ├─ index.ts
│ │ └─ Find.query.ts
│ │ ├─ Find.handler.ts
│ │ └─ Find.handler.test.ts
│ └─ presentation
│ ├─ dto
│ │ ├─ index.ts
│ │ ├─ MainResponseBundle.dto.ts
│ │ │ └─ MainResponseBundle.dto.test.ts
│ │ ├─ ProductCatalog.dto.ts
│ │ │ └─ ProductCatalog.dto.test.ts
│ │ └─ ProductSummary.dto.ts
│ │ └─ ProductSummary.dto.test.ts
│ └─ controllers
│ ├─ index.ts
│ └─ Main.controller.ts
│ └─ Main.controller.test.ts
├─ product
│ ├─ module.ts
│ ├─ application
│ │ ├─ command
│ │ │ ├─ index.ts
│ │ │ ├─ Edit.command.ts
│ │ │ │ ├─ Edit.handler.ts
│ │ │ │ └─ Edit.handler.test.ts
│ │ │ ├─ ImageRegister.command.ts
│ │ │ │ ├─ ImageRegister.handler.ts
│ │ │ │ └─ ImageRegister.handler.test.ts
│ │ │ ├─ OptionEdit.command.ts
│ │ │ │ ├─ OptionEdit.handler.ts
│ │ │ │ └─ OptionEdit.handler.test.ts
│ │ │ ├─ OptionRegister.command.ts
│ │ │ │ ├─ OptionRegister.handler.ts
│ │ │ │ └─ OptionRegister.handler.test.ts
│ │ │ ├─ OptionRemove.command.ts
│ │ │ │ ├─ OptionRemove.handler.ts
│ │ │ │ └─ OptionRemove.handler.test.ts
│ │ │ ├─ Register.command.ts
│ │ │ │ ├─ Register.handler.ts
│ │ │ │ └─ Register.handler.test.ts
│ │ │ └─ Remove.command.ts
│ │ │ ├─ Remove.handler.ts
│ │ │ └─ Remove.handler.test.ts
│ │ └─ query
│ │ ├─ index.ts
│ │ ├─ Find.query.ts
│ │ │ ├─ Find.handler.ts
│ │ │ └─ Find.handler.test.ts
│ │ └─ FindAll.query.ts
│ │ ├─ FindAll.handler.ts
│ │ └─ FindAll.handler.test.ts
│ └─ presentation
│ ├─ dto
│ │ ├─ index.ts
│ │ ├─ model
│ │ │ ├─ Brand.dto.ts
│ │ │ │ └─ Brand.dto.test.ts
│ │ │ ├─ Image.dto.ts
│ │ │ │ └─ Image.dto.test.ts
│ │ │ ├─ Product.dto.ts
│ │ │ │ └─ Product.dto.test.ts
│ │ │ ├─ ProductDetail.dto.ts
│ │ │ │ └─ ProductDetail.dto.test.ts
│ │ │ ├─ ProductOption.dto.ts
│ │ │ │ └─ ProductOption.dto.test.ts
│ │ │ ├─ ProductOptionGroup.dto.ts
│ │ │ │ └─ ProductOptionGroup.dto.test.ts
│ │ │ ├─ ProductPrice.dto.ts
│ │ │ │ └─ ProductPrice.dto.test.ts
│ │ │ ├─ Seller.dto.ts
│ │ │ │ └─ Seller.dto.test.ts
│ │ │ └─ Tag.dto.ts
│ │ │ └─ Tag.dto.test.ts
│ │ ├─ request
│ │ │ ├─ ProductBody.dto.ts
│ │ │ │ └─ ProductBody.dto.test.ts
│ │ │ ├─ ProductQuery.dto.ts
│ │ │ │ └─ ProductQuery.dto.test.ts
│ │ │ ├─ ProductOptionBody.dto.ts
│ │ │ └─ ProductOptionImageBody.dto.ts
│ │ └─ response
│ │ ├─ ProductResponse.dto.ts
│ │ │ └─ ProductResponse.dto.test.ts
│ │ └─ ProductResponseBundle.dto.ts
│ │ └─ ProductResponseBundle.dto.test.ts
│ └─ controllers
│ ├─ index.ts
│ ├─ Product.controller.ts
│ │ └─ Product.controller.test.ts
│ └─ Product_Options.controller.ts
│ └─ Product_Options.controller.test.ts
├─ category
│ ├─ module.ts
│ ├─ application
│ │ └─ query
│ │ ├─ index.ts
│ │ ├─ FindAll.query.ts
│ │ │ ├─ FindAll.handler.ts
│ │ │ └─ FindAll.handler.test.ts
│ │ └─ FindProducts.query.ts
│ │ ├─ FindProducts.handler.ts
│ │ └─ FindProducts.handler.test.ts
│ └─ presentation
│ ├─ dto
│ │ ├─ index.ts
│ │ ├─ Category.dto.ts
│ │ │ └─ Category.dto.test.ts
│ │ ├─ CategoryQuery.dto.ts
│ │ │ └─ CategoryQuery.dto.test.ts
│ │ ├─ CategoryResponseBundle.dto.ts
│ │ │ └─ CategoryResponseBundle.dto.test.ts
│ │ └─ NestedCategory.dto.ts
│ │ └─ NestedCategory.dto.test.ts
│ └─ controllers
│ ├─ index.ts
│ └─ Category.controller.ts
│ └─ Category.controller.test.ts
└─ review
├─ module.ts
├─ application
│ ├─ command
│ │ ├─ index.ts
│ │ ├─ Edit.command.ts
│ │ │ ├─ Edit.handler.ts
│ │ │ └─ Edit.handler.test.ts
│ │ ├─ Register.command.ts
│ │ │ ├─ Register.handler.ts
│ │ │ └─ Register.handler.test.ts
│ │ └─ Remove.command.ts
│ │ ├─ Remove.handler.ts
│ │ └─ Remove.handler.test.ts
│ └─ query
│ ├─ index.ts
│ └─ Find.query.ts
│ ├─ Find.handler.ts
│ └─ Find.handler.test.ts
└─ presentation
├─ dto
│ ├─ index.ts
│ ├─ Review.dto.ts
│ │ └─ Review.dto.test.ts
│ ├─ ReviewBody.dto.ts
│ │ └─ ReviewBody.dto.test.ts
│ ├─ ReviewQuery.dto.ts
│ │ └─ ReviewQuery.dto.test.ts
│ ├─ ReviewResponse.dto.ts
│ │ └─ ReviewResponse.dto.test.ts
│ ├─ ReviewResponseBundle.dto.ts
│ │ └─ ReviewResponseBundle.dto.test.ts
│ ├─ ReviewSummary.dto.ts
│ │ └─ ReviewSummary.dto.test.ts
│ └─ User.dto.ts
└─ controllers
├─ index.ts
└─ Review.controller.ts
└─ Review.controller.test.ts
Docker Compose를 활용하여 서버와 데이터베이스를 각각 별도의 컨테이너로 구성하고,
공통 네트워크 환경에서 실행되도록 설정합니다.
개발 및 테스트 환경에서의 서비스 간 통신을 간편하게 구성합니다.
# 애플리케이션
$ docker compose -f docker-compose.streaming.yml -f docker-compose.yml up -d
# UI 모니터링
$ docker compose -f docker-compose.tools.yml up -d
서버는 환경변수 파일(.env)에 정의된 PORT
번호를 통해 외부 호스트에서 접근할 수 있습니다.
기본 포트는 3000
으로 설정되어 있으며, 로컬 환경에서 서버에 접속하려면 다음 주소를 이용합니다.
- api 접속: http://localhost:3000
- Swagger 문서: http://localhost:3000/swagger-ui/index.html
- Kafka 운용UI: http://localhost:8085/
- ElasticSearch 지표 시각화: http://localhost:5601/