Skip to content

NarciSource/Pre-Onboarding-Challenge-BE-31

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CQRS 시스템 설계/구축 챌린지

원티드 프리온보딩 챌린지 백엔드 31차

GitHub release openapi test

기술 스택

ksqlDB Apache Kafka Apicurio Registry Apache Zookeeper Debezium
NestJS NodeJS TypeScript
redis elasticsearch Mongodb PostgreSQL TypeORM
Codecov Jest Testcontainers Swagger
Github Actions ESLint Prettier
Docker Compose Docker

개발문서

API 명세서

본 프로젝트의 API 명세서는 GitHub Pages을 통해 Swagger UI로 제공됩니다.

Swagger
   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} 리뷰 삭제

다이어그램

System Architecture Diagram

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"
Loading

Debezium – Kafka – ksqlDB 기반 실시간 CQRS 아키텍처

  1. API Layer

    • API Server: 클라이언트로부터 Command(쓰기 요청)와 Query(읽기 요청)를 처리하는 진입점.
      Command 요청은 Command Side를 통해 처리하고, Query 요청은 Query Side를 통해 처리.

    • Redis: API 레이어에서 읽기 요청의 응답 속도를 높이기 위해 캐시로 사용.
      API 서버와 양방향으로 연결되어 캐시 조회 및 갱신 수행.

  2. Command Side

    • PostgreSQL: 시스템의 쓰기 전용 데이터 저장소.
      Command Side의 영속 데이터가 저장되며, 변경 사항은 CDC로 감지됨.
  3. CDC (Change Data Capture)

    • Debezium (PostgreSQL Connector): PostgreSQL의 WAL(Write-Ahead Log)을 읽어 변경 이벤트를 추출.
      추출된 이벤트를 Kafka 토픽으로 발행.
  4. Messaging

    • Kafka: 모든 변경 이벤트와 스트리밍 데이터를 전달하는 중앙 메시징 플랫폼.
      Debezium에서 발행한 CDC 이벤트를 토픽에 저장하고, 이를 소비자(ksqlDB, Sink Connector 등)가 구독하도록 함.
  5. Streaming Processing

    • ksqlDB: Kafka 토픽의 실시간 스트림을 읽어 변환, 필터링, 집계 등 스트리밍 처리 수행.
      처리된 결과를 새로운 Kafka 토픽에 발행하여 후속 시스템에서 사용 가능하게 함.
  6. 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) 조회 요청 처리.

  7. 데이터 흐름 요약

    • 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
Loading

 

Entity Relationship Diagram

erd

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
Loading

테스트 리포트

테스트 통과 여부와 커버리지 현황은 시각적으로 제공됩니다.

Jest Codecov
테스트 리포트 바로가기 커버리지 대시보드 바로가기

커버리지는 Codecov를 통해 분석됩니다.
codecov

Sunburst-graph

폴더 구조

열기
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으로 설정되어 있으며, 로컬 환경에서 서버에 접속하려면 다음 주소를 이용합니다.

About

CQRS 시스템 설계/구축 챌린지

Topics

Resources

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages