Skip to content
ย 
ย 

Latest commit

ย 

History

799 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

ON SURVEY โ€” ํ† ์Šค ๋ฏธ๋‹ˆ์•ฑ ์„ค๋ฌธ ์กฐ์‚ฌ ํ”Œ๋žซํผ

์„ค๋ฌธ์„ ๋งŒ๋“ค๊ณ  ์‹ถ์€ ์‚ฌ๋žŒ๊ณผ ์‘๋‹ตํ•˜๊ณ  ์ฐธ์—ฌ ๋ณด์ƒ์„ ๋ฐ›๊ณ  ์‹ถ์€ ์‚ฌ๋žŒ์„ ์—ฐ๊ฒฐํ•˜๋Š” ์„œ๋น„์Šค. ํ† ์Šค ์•ฑ ์•ˆ์—์„œ ๋™์ž‘ํ•˜๋Š” ๋ฏธ๋‹ˆ์•ฑ(Apps in Toss)์œผ๋กœ, ๊ฒฐ์ œยท์ธ์ฆยทํฌ์ธํŠธ ์ง€๊ธ‰์ด ๋ชจ๋‘ ํ† ์Šค ํ”Œ๋žซํผ๊ณผ ์—ฐ๋™๋ฉ๋‹ˆ๋‹ค.

โš™๏ธย ๊ธฐ์ˆ  ์Šคํƒ

Language / Runtime Java 21, Spring Boot 3.5.6
Persistence MySQL 8(AWS RDS), Spring Data JPA, QueryDSL 7
Cache / Lock Redis 7, Redisson(๋ถ„์‚ฐ๋ฝ)
Security Spring Security
Infra Docker, Nginx, NCP Server, NCP Object Storage, AWS Lambda
CI/CD GitHub Actions
Observability Sentry, Discord Webhook, Spring Actuator

1. ์–ด๋–ค ์„œ๋น„์Šค์ธ๊ฐ€

flowchart LR
  A["์„ค๋ฌธ ์˜๋ขฐ์ž"] -->|"์ฝ”์ธ ์ถฉ์ „ (ํ† ์Šค IAP)"| B["์„ค๋ฌธ ์ƒ์„ฑ<br/>๋ฌธํ•ญยทํƒ€๊ฒŒํŒ…ยท๋ชฉํ‘œ ์‘๋‹ต ์ˆ˜ ์„ค์ •"]
  B --> C["๊ฒ€์ˆ˜ ํ›„ ๊ฒŒ์‹œ"]
  C --> D["์ฐธ์—ฌ์ž์—๊ฒŒ ๋…ธ์ถœ"]
  D --> E["์Šคํฌ๋ฆฌ๋‹ ํ†ต๊ณผ โ†’ ์‘๋‹ต"]
  E --> F["๋ชฉํ‘œ ์‘๋‹ต ์ˆ˜ ๋„๋‹ฌ ์‹œ ์ž๋™ ๋งˆ๊ฐ"]
  E --> G["์ฐธ์—ฌ์ž์—๊ฒŒ ํ”„๋กœ๋ชจ์…˜ ์ง€๊ธ‰"]
  F --> H["์˜๋ขฐ์ž: ๊ฒฐ๊ณผ ์ง‘๊ณ„ยทCSV ๋‹ค์šด๋กœ๋“œ"]
Loading

2. ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

flowchart TB
  subgraph CLIENT["ํด๋ผ์ด์–ธํŠธ"]
    TOSS["ํ† ์Šค ๋ฏธ๋‹ˆ์•ฑ WebView"]
    BO["๋ฐฑ์˜คํ”ผ์Šค ๋ธŒ๋ผ์šฐ์ €"]
  end
 
  subgraph SERVER
    NGX["Nginx 1.25<br/>TLS ์ข…๋ฃŒ ยท ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ"]
    BLUE["onsurvey-blue :8080"]
    GREEN["onsurvey-green :8081"]
    REDIS[("Redis 7<br/>๋ถ„์‚ฐ๋ฝ ยท ์นด์šดํ„ฐ")]
  end
 
  subgraph DATA["๋ฐ์ดํ„ฐ"]
    RDS[("AWS RDS<br/>MySQL 8")]
    OBS[("NCP Object Storage<br/>์ด๋ฏธ์ง€ ยท ๋ณ€ํ™˜ JSON")]
  end
 
  subgraph EXT["์™ธ๋ถ€ ์—ฐ๋™"]
    TAPI["Toss Partner API (mTLS)<br/>OAuth2 ยท IAP ยท ํ”„๋กœ๋ชจ์…˜ ยท ํ‘ธ์‹œ"]
    LAMBDA["AWS Lambda<br/>๊ตฌ๊ธ€ํผ ํŒŒ์‹ฑ"]
    SENTRY["Sentry"]
    DISCORD["Discord Webhook"]
  end
 
  TOSS --> NGX
  BO --> NGX
  NGX -->|"active"| BLUE
  NGX -.->|"standby"| GREEN
  BLUE --> REDIS
  BLUE --> RDS
  BLUE --> OBS
  BLUE --> TAPI
  BLUE --> LAMBDA
  BLUE --> SENTRY
  BLUE --> DISCORD
  LAMBDA -->|"ํŒŒ์‹ฑ ๊ฒฐ๊ณผ ์ ์žฌ"| OBS
Loading

3. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์กฐ

ํŒจํ‚ค์ง€๋ฅผ domain(๋น„์ฆˆ๋‹ˆ์Šค) ๊ณผ global(๊ณตํ†ตยท์™ธ๋ถ€์—ฐ๋™) ๋กœ ๋‚˜๋ˆ„๊ณ , ์™ธ๋ถ€ ์‹œ์Šคํ…œ์— ์˜์กดํ•˜๋Š” ์˜์—ญ์€ ํฌํŠธ-์–ด๋Œ‘ํ„ฐ ๋ฐฉ์‹์œผ๋กœ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ตฌํ˜„์„ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

OneQ/OnSurvey/
โ”œโ”€โ”€ domain/                        # ๋ฐ”์šด๋””๋“œ ์ปจํ…์ŠคํŠธ
โ”‚   โ”œโ”€โ”€ survey/                    # ์„ค๋ฌธ ์ƒ์„ฑยท๊ฒŒ์‹œยท๊ด€๋ฆฌยท์ง‘๊ณ„ยทํ™˜๋ถˆ
โ”‚   โ”œโ”€โ”€ question/                  # ๋ฌธํ•ญ 11์ข… ๋ฐ ๋ณด๊ธฐยท์„น์…˜
โ”‚   โ”œโ”€โ”€ participation/             # ์Šคํฌ๋ฆฌ๋‹ยท์‘๋‹ต ์ €์žฅยท์‘๋‹ต ์™„๋ฃŒ
โ”‚   โ”œโ”€โ”€ member/                    # ํšŒ์›ยท์ฝ”์ธยทํฌ์ธํŠธ
โ”‚   โ”œโ”€โ”€ discount/                  # ํ• ์ธ ์ฝ”๋“œ
โ”‚   โ””โ”€โ”€ admin/                     # ๋ฐฑ์˜คํ”ผ์Šค (port/adapter ๊ตฌ์กฐ)
โ””โ”€โ”€ global/
    โ”œโ”€โ”€ auth/                      # ์ธ์ฆ ์ „๋žต ยท ํ•„ํ„ฐ ยท ํ† ํฐ
    โ”œโ”€โ”€ payment/  promotion/  push/   # ํ† ์Šค ์—ฐ๋™ ์œ ์Šค์ผ€์ด์Šค (port/out)
    โ”œโ”€โ”€ infra/                     # toss ยท redis ยท ncp ยท discord ยท transaction
    โ””โ”€โ”€ common/                    # config ยท exception ยท response ยท util
flowchart TB
  subgraph L1["Presentation"]
    C1["REST Controller"]
    C2["Backoffice (Thymeleaf SSR)"]
  end
  subgraph L2["Security Filter Chain"]
    F1["#1 /connect-out โ€” ๊ณ ์ • ํ—ค๋” ๊ฒ€์ฆ"]
    F2["#2 ๋ฐฑ์˜คํ”ผ์Šค โ€” ์„ธ์…˜ + RateLimiter"]
    F3["#3 ๊ทธ ์™ธ โ€” STATELESS + AuthStrategy"]
  end
  subgraph L3["Application"]
    A1["Facade/UseCase"]
    A2["Command/Query ์„œ๋น„์Šค ๋ถ„๋ฆฌ"]
  end
  subgraph L4["Domain"]
    D1["Entity"]
    D2["Port(out) ์ธํ„ฐํŽ˜์ด์Šค"]
  end
  subgraph L5["Infrastructure"]
    I1["JPA + QueryDSL"]
    I2["TossApiClient (WebClient / mTLS)"]
    I3["RedisAgent (Redisson)"]
    I4["NcpS3Client / Discord Notifier"]
  end

  C1 --> F3 --> A1
  C2 --> F2 --> A1
  A1 --> A2 --> D1
  A1 --> D2
  D2 -. ๊ตฌํ˜„ .-> I2
  D2 -. ๊ตฌํ˜„ .-> I1
  A2 --> I3
  A1 --> I4
Loading

์˜๋„

  • Command / Query ๋ถ„๋ฆฌ โ€” ์กฐํšŒ๋Š” @Transactional(readOnly = true)๋กœ ์žก๊ณ  QueryDSL Projection์œผ๋กœ ํ•„์š”ํ•œ ์ปฌ๋Ÿผ๋งŒ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์“ฐ๊ธฐ ๊ฒฝ๋กœ๋Š” ์ •ํ•ฉ์„ฑ, ์ฝ๊ธฐ ๊ฒฝ๋กœ๋Š” ์‘๋‹ต์†๋„๋ฅผ ์ค‘์ ์œผ๋กœ ๋ชจ๋ธ์„ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.
  • RedisAgent๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค(RedisLockAction, RedisCacheAction)๋กœ ๋…ธ์ถœ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ํ•„ํ„ฐ ์ฒด์ธ 3๋ถ„๋ฆฌ โ€” ์‚ฌ์šฉ์ž์™€ ์šด์˜์ž์˜ ์ธ์ฆ ๋ฐฉ์‹์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ๊ฐ ์ธ์ฆ ๋ฐฉ์‹์— ๋”ฐ๋ผ@Order๊ฐ€ ๋‹ค๋ฅธ SecurityFilterChain ์„ธ ๊ฐœ๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

4. ๋„๋ฉ”์ธ ๋ชจ๋ธ

erDiagram
    MEMBER ||--o{ SURVEY : "์ƒ์„ฑ"
    MEMBER ||--o{ RESPONSE : "์ฐธ์—ฌ"
    MEMBER ||--o{ PAYMENT : "๊ฒฐ์ œ"
    MEMBER ||--o{ PROMOTION_GRANT : "ํ”„๋กœ๋ชจ์…˜ ์ˆ˜๋ น"

    SURVEY ||--|| SURVEY_INFO : "ํƒ€๊ฒŒํŒ…/๋ชฉํ‘œ์‘๋‹ต์ˆ˜"
    SURVEY ||--o{ SECTION : "๊ตฌ์„ฑ"
    SURVEY ||--o{ QUESTION : "ํฌํ•จ"
    SURVEY ||--o| SCREENING : "์‚ฌ์ „์งˆ๋ฌธ"
    SURVEY ||--o{ RESPONSE : "์‘๋‹ต ์ด๋ ฅ"

    QUESTION ||--o{ CHOICE_OPTION : "๊ฐ๊ด€์‹ ๋ณด๊ธฐ"
    QUESTION ||--o{ GRID_OPTION : "๊ทธ๋ฆฌ๋“œ ํ–‰/์—ด"
    QUESTION ||--o{ QUESTION_ANSWER : "๋‹ต๋ณ€"
    SCREENING ||--o{ SCREENING_ANSWER : "๋‹ต๋ณ€"

    SURVEY_INFO }o--o| DISCOUNT_CODE : "ํ• ์ธ ์ ์šฉ"

    MEMBER {
        bigint member_id PK
        bigint user_key UK "ํ† ์Šค ์‚ฌ์šฉ์ž ์‹๋ณ„์ž"
        string name
        enum   gender
        enum   status "ACTIVE / WITHDRAWN ๋“ฑ"
        bigint coin "์ถฉ์ „ ์ฝ”์ธ"
        bigint promotion_point "์ง€๊ธ‰ ํ”„๋กœ๋ชจ์…˜"
        bool   onboarding_completed
    }
    SURVEY {
        bigint id PK
        bigint member_id FK
        text   title
        datetime deadline
        enum   status "WRITING/ONGOING/CLOSED/REFUNDED/REVIEW"
        bool   is_temporary
        bool   is_free
        int    total_coin
    }
    SURVEY_INFO {
        bigint info_id PK
        bigint survey_id FK
        int    due_count "๋ชฉํ‘œ ์‘๋‹ต ์ˆ˜"
        int    completed_count
        enum   gender "ํƒ€๊ฒŒํŒ… ์กฐ๊ฑด"
        int    promotion_amount
        bool   refundable
    }
    QUESTION {
        bigint question_id PK
        bigint survey_id FK
        string type "๋‹จ์ผ ํ…Œ์ด๋ธ” ์ƒ์† ํŒ๋ณ„์ž"
        int    question_order
        int    section
        bool   is_required
    }
    RESPONSE {
        bigint survey_id PK "๋ณตํ•ฉ PK"
        bigint member_id PK
        bool   is_responded
        bool   is_screened
    }
    PROMOTION_GRANT {
        bigint id PK
        bigint user_key "UK(user_key, survey_id, promotion_code)"
        bigint survey_id
        string promotion_code
        enum   status "PENDING/SUCCESS/FAILED"
        string exec_key "ํ† ์Šค ์‹คํ–‰ ํ‚ค"
        bool   point_granted "ํฌ์ธํŠธ ๋ฐ˜์˜ ์—ฌ๋ถ€"
    }
    PAYMENT {
        bigint payment_id PK
        string order_id "ํ† ์Šค ์ฃผ๋ฌธ ๋ฒˆํ˜ธ"
        bigint user_key
        int    total_amount
        enum   status "PENDING/PAID/REFUNDED"
        enum   purpose "COIN_CHARGE/HOME_REGISTRATION"
    }
Loading
  • ๋ฌธํ•ญ 11์ข…์„ ๋‹จ์ผ ํ…Œ์ด๋ธ” ์ƒ์†(SINGLE_TABLE)์œผ๋กœ โ€” ๊ฐ๊ด€์‹, ํ‰์ , NPS, ๊ทธ๋ฆฌ๋“œ, ๋‚ ์งœ, ์‹œ๊ฐ„ ๋“ฑ ํƒ€์ž…๋งˆ๋‹ค ํ•„์š”ํ•œ ์†์„ฑ์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค. ์กฐ์ธ ์ „๋žต์„ ์“ฐ๋ฉด ๋ฌธํ•ญ ์กฐํšŒ๋งˆ๋‹ค ์กฐ์ธ์ด ๋ถ™๋Š”๋ฐ, ์„ค๋ฌธ ์ฐธ์—ฌ ์‹œ ๋‹ค๊ฑด์˜ ๋ฌธํ•ญ ์กฐํšŒ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ์กฐ์ธ ๋น„์šฉ์„ ์ตœ์†Œํ™”ํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ํŠธ๋ ˆ์ด๋“œ ์˜คํ”„๋กœ ํ•˜์œ„ ํƒ€์ž…์˜ ์ปฌ๋Ÿผ์ด nullable์ด ๋˜๋Š” ๊ฒƒ์„ ๊ฐ์ˆ˜ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • RESPONSE์˜ ๋ณตํ•ฉ PK (survey_id, member_id) โ€” ํ•œ ์‚ฌ๋žŒ์€ ํ•œ ์„ค๋ฌธ์— ํ•œ ๋ฒˆ๋งŒ ์‘๋‹ตํ•˜๋„๋ก ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ ˆ๋ฒจ์—์„œ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.
  • PROMOTION_GRANT์˜ ๋ณตํ•ฉ ์œ ๋‹ˆํฌ ์ œ์•ฝ โ€” ํฌ์ธํŠธ ์ค‘๋ณต ์ง€๊ธ‰์„ ๋ง‰๋„๋ก ์œ ๋‹ˆํฌ ์ œ์•ฝ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • SURVEY์™€ SURVEY_INFO ๋ถ„๋ฆฌ โ€” ์„ค๋ฌธ ๋ณธ๋ฌธ(์ œ๋ชฉ, ๋ฌธํ•ญ)์€ ์ž‘์„ฑ ์ค‘ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๊ณ  ํƒ€๊ฒŒํŒ…, ๊ฐ€๊ฒฉ ์ •๋ณด ๋“ฑ์€ ๊ฒฐ์ œ ๋ฐ ๊ฒŒ์‹œ ์‹œ์ ์— ํ™•์ •๋ฉ๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ ์ฃผ๊ธฐ๊ฐ€ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„๋ฆฌํ•ด ์ž ๊ธˆ ๊ฒฝํ•ฉ์„ ์ค„์˜€์Šต๋‹ˆ๋‹ค.

About

๐ŸŒŠ๐Ÿ“ข OnSurvey Server ๐Ÿ“ข๐ŸŒŠ

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages