SPMS_API/SPMS.API/Documents/ServerDevelopmentGuide.md

20 KiB
Raw Permalink Blame History

1. 기술 스택 및 환경

기술 스택
Framework .NET 9.0 / ASP .NET Core
Database MariaDB (Server Version AutoDetect)
ORM Entity Framework Core (Code-First)
API Docs Swagger (OpenAPI)
Cache Redis
MQ RabbitMQ
Logging Serilog
Testing xUnit, Moq

2. 코드 컨벤션

2.1. 명명 규칙

  • PascalCase (대문자 시작): 클래스명, 메서드명, 프로퍼티명, 파일명, public 필드
  • camelCase (소문자 시작): 로컬 변수, 매개 변수
  • _camelCase (언더바 시작): private 필드
  • Interface 는 반드시 접두사 I 를 붙인다.
  • 비동기 메서드는 접미사 Async 를 붙인다.

2.2. 문법 규칙

  • var 사용: 자료형을 명확하게 우변에서 확인 가능할 경우에는 var를 적극 사용한다.

    • 리턴 타입이 뭔지 모르는 경우에는 명시를 해준다.
      var user = new User(); // 가능  
    
      var count = GetCount(); // 불가능  
      int count = GetCount(); // 리턴 타입을 명시해줄 것
    
  • 비동기(Asnyc/Await) 동작: I/O 작업(DB, API호출, 파일)은 무조건 비동기로 작성한다.

    • 비동기 동작을 하는 메서드의 이름 뒤에는 Asnyc 를 붙인다.
    • void 대신 Task 또는 Task<T>를 반환한다.
  • LINQ: 가독성을 해치지 않는 선에서 적극 사용하되, 복잡한 쿼리는 쿼리 구문이나 분할 작성 할 것

  • 주석 및 문서화 (Swagger): API 정의서와의 동기화를 위해 Controller와 DTO의 모든 Public 멤버에는 반드시 XML 주석(/// <summary>)을 작성해야 한다.


3. 아키텍쳐 및 개발 표준

3.1. 개요

  • 의존성의 방향은 항상 외부에서 내부로만 향한다.
  • 프로젝트 분리: 프로젝트는 클린 아키텍쳐를 기반으로 하여 물리적으로 4개의 .csproj 로 분리해 참조 규칙을 강제한다.


  • 클린 아키텍쳐 샘플 이미지
    CleanArchitecture.png

3.2. 계층 구조 (Layers)

  • 계층의 정의는 내부에서 외부 순서로 정의한다.

3.2.1. 계층 (Layers)

1. Domain (Entities, Enterprise Business Rules)

  • 역할: 비즈니스의 핵심 개념, 기업 업무 규칙(Entity)을 정의한다.
  • 특징: 외부 라이브러리 (DB, Web, 등)에 대한 의존성이 없어야 한다. 순수한 C# 클래스로만 구성된다.
  • 요소: Entites, Value Objects, Enums, Domain Exceptions, Repository Interfaces (선택)

2. Application (Use cases, Application Business Rules)

  • 역할: 애플리케이션의 비즈니스 로직을 처리하고 도메인과 외부를 연결하는 오케스트레이션 담당한다.
  • 특징: 도메인 계층에만 의존하며, 인터페이스를 정의하고 구현은 외부 계층(Infra Structure)에 맡긴다.
  • 요소: Service Interfaces, DTOs, Mappers, Validators, Service, Implementations (pure 로직)

3. Infra Structure (Interface Adapters)

  • 역할: 애플리케이션 계층에서 정의한 인터페이스를 실제로 구현한다. DB, 외부 API, 파일 시스템 등 외부 세계와 통신한다.
  • 특징: 애플리케이션과 도메인을 참조하고 다양한 라이브러리를 사용한다.
  • 요소: Repository 구현체, DB Context, 외부 API Client, Migrations

4. Presentation (Frameworks & Drivers)

  • 역할: 사용자의 요청(HTTP)을 받아 애플리케이션 계층으로 전달하고 결과를 반환한다.
  • 특징: 로직을 가지지 않으며 애플리케이션에 의존한다.
  • 요소: Controller, Middlewares, Filters, Program.cs (DI 설정)

3.2.2. 프로젝트 참조 규칙

  • 프로젝트 내에서 참조 설정을 시스템 상에서 해줘서 파일들 간의 물리적인 연결 고리를 룰에 맞게 설정해 줘야 한다.
    • Domain: 참조 없음
    • Application: Domain
    • Infra Structure: Application, Domain
    • Presentation: Application, Infra Structure

3.3. 개발 상세 가이드

3.3.1. API 프로토콜 및 데이터 규격

  • 클라이언트와 서버 간의 모든 통신은 아래 정의된 Header와 Body 규격을 준수해야 한다.

1. 요청 규격 (Request)

  • 클라이언트는 API 호출 시, HTTP Header에 다음 필수 메타데이터를 포함해야 한다.
    • 메타데이터들은 로그 컨텍스트 식별을 위해 평문으로 전송한다.
헤더 명 설명 및 용도 예시
Content-Type 전송 데이터 포맷 application/octet-stream
Authorization JWT 인증 토큰
- L2 등급 이상 필수
Bearer abcdefg…
X-API-KEY 프로젝트 식별 및 데이터 격리를 위한 고유 키 (테넌트 식별자) spms_api_custom_key
X-Request-ID 트랜잭션 추적 ID
- 매요청 마다 고유값 생성
- 재사용 금지
abcdefg-12aa…

2. 응답 규격 (Response)

  • 서버는 비즈니스 로직의 결과와 상관 없이 항상 아래의 공통 JSON포맷으로 응답한다.
    • 단, Body의 내용물인 data 필드는 보안 등급에 따라 암호화될 수 있다.
      {  
          "req_id": "abcdefg-12aa…",            "result": "success",            "code": "0000",            "msg": "Response Success MESSAGE",            "data": { ... }        }  
      ```        - req_id: 요청 헤더의 X-Request-ID를 그대로 반환 (없을 경우 서버 생성), 비동기 응답 식별용
        - result: API 호출 결과에 따라 성공(success) / 실패(fail) 여부 반환
        - code: API 명세서에 선언된 코드  반환 (성공시 0000)
        - msg: 현재 동작에 대한 결과 메시지 반환
        - data: 현재 동작에 대한 데이터 반환 (E2EE 적용시 암호화된 문자열)
      
      

3. 데이터 매핑 원칙

  • Entity 노출 금지 (Layer: Domain)
    • DB 테이블과 매핑되거나 핵심 로직을 가진 객체로, 절대 Controller 밖으로 노출하지 않는다.
  • DTO 필수 (Layer: Application): 데이터 전송을 위한 껍데기 객체
    • Controller는 반드시 Request DTO 를 받고 Response DTO 를 반환해야 한다.
    • API 스펙 변경이 도메인 모델에 영향을 주어서는 안되며 그 반대의 경우도 마찬가지이다.
  • Body 암호화 정책

3.3.2. 비즈니스 로직과 의존성 주입 (DI)

1. 비즈니스 로직 위치

  • 단순 데이터 조회 / 저장
    • Repository (Layer: Infra Structure)에서 담당하되, 내부 구현 로직이 없어야 한다.
  • 업무 규칙 (유효성 검사, 계산, 흐름 제어)
    • 반드시 Service (Layer: Application) 또는 Entity (Layer: Domain) 내부에 위치해야 한다.
  • Controller
    • 요청을 받고 Service를 호출하고 결과를 DTO로 변환하는 역할만 수행한다.

2. 의존성 역전 원칙 (DIP)

  • Interface 정의(Layer: Application)
    • 특정한 동작에 대한 정의서를 Application 계층에서 선언한다.
    • 예: 유저 정보를 저장하는 기능의 필요 = IUserRepository
  • 구현(Layer: Infra Structure)
    • 해당 동작의 특별한 동작 구현을 Infra Structure 계층에서 구현한다.
    • 예: 저장 기능을 EF Core로 구현 = UserRepository
  • 주입 (Layer: Presentation)
    • Program.cs 에서 DI를 통해 둘을 연결해준다.
    • 예: builder.Services.AddScoped<IUserRepository, UserRepository>();

3.3.3. 데이터베이스 접근 (EF Core)

  1. Code-First
    • C# 엔티티 코드가 메인이 되며 Migration 명령어로 DB를 업데이트 한다.
  2. Fluent API 사용
    • Entity 클래스의 순수성을 위해 [Key], [Table] 과 같은 어트리뷰트 대신 DbContext 내 OnModelCreating 또는 별도의 IEntityTypeConfiguration 파일에서 설정한다.
  3. Production 배포 전략
    • 개발/스테이징 환경에서는 자동 마이그레이션을 허용하나, 운영(Production) 환경에서는 절대 Database.Migrate() 자동 실행을 금지한다.
  4. Script Migration
    • 반드시 dotnet ef migrations script 명령어로 SQL 스크립트를 생성하여, DBA 또는 관리자의 검수를 거친 후 수동/CI 파이프라인을 통해 적용한다.

3.3.4. 유효성 관리

  • FluentValidation 사용: 잦은 if문 대신 AbstractValidator<T> 를 상속 받은 별도 클래스로 분리한다.
  • 동작: Controller 진입 전 또는 Service 실행 시점에 자동 검증한다.
  • 위치: SPMS.Application/Validators

3.3.5. 트랜잭션 관리

  • 서비스 단위: 하나의 Service 메서드가 하나의 트랜잭션 단위가 된다.
  • 원자성 (Atomicity): 원자적인 작업 단위로 모든 작업이 성공적으로 완료되지 않는다면, 즉 하나라도 실패하면 전체 롤백한다.

3.3.6. 예외 처리

  • Global Handling: 개별 메서드에서 try-catch로 에러를 삼키지 않는다.
  • Custom Exception: 비즈니스 로직 에러는 의도적으로 throw new BusinessExeption(”msg”)을 발생시킨다
  • Middleware: 전역 미들웨어에서 에러를 잡아 공통 포맷으로 변환하여 응답한다.

3.3.7. 인증 및 접근 제어

  • 시스템 접근 권한은 Who, Where, How often 의 3중 체계로 검증한다.

1. 인증 - JWT

  • Stateless 구조를 위해 JWT 방식을 표준으로 한다.
  • 구현
    • Authorization 헤더에 Bearer Token 을 담아 전송하며 만료 시 Refresh Token 으로 갱신한다.

2. 인가 - 역할 기반 엑세스 제어 (RBAC)

  • 사용자의 Role(Admin, Manager, User) 에 따라 API 접근 권한을 엄격히 분리한다.
  • 구현
    • Controller 상단에 [Authorize(Roles="...")] 어트리뷰트를 명시해 코드 레벨에서 권한을 강제한다.

3. 네트워크 접근 제어

  • API Key가 탈취되더라도 허용되지 않은 IP에서의 접근을 원천 차단한다.
  • 구현
    • 모든 API요청 시 미들웨어에서 클라이언트 IP를 추출한다.
    • ServiceIP 테이블에 등록된 IP 대역인지 검증하고 불일치 시 403 Forbidden 처리한다.

4. API 속도 제한

  • Dos 공격 및 루프로 인한 과부하 방지
  • AspNetCoreRateLimit 을 사용하여 IP 주소 또는 API Key 별로 초당/분당 요청 쿼터를 제한한다.(초과시 429 Too Many Requests)

3.3.8. 데이터 보안 및 암호화

  • 데이터의 생명 주기 (저장, 전송) 전반에 걸쳐 데이터 등급에 따른 암호화 정책을 적용한다.

1. 데이터 등급 분류

등급 암호화 방식 대상
L1 (Public) 평문 공지사항, 일반 리소스
L2 (Authenticated) HTTPS + JWT 일반 서비스 데이터
L3 (Sensitive) HTTPS + E2EE + AES-256 저장 API Key, 토큰
L4 (Critical) HTTPS + E2EE + BCrypt 저장 비밀번호

2. 암호화 - 저장

  • 양방향 암호화: 외부 연동에 필요한 민감 정보는 AES-256 (CBC/GCM) 알고리즘으로 암호화해 DB에 저장한다.
  • 단방향 해시: 비밀번호 등 복호화가 불필요한 정보는 BCrypt 알고리즘(Salt 포함)을 사용해 해싱 저장한다. (절대 평문 저장 금지)
  • Key 관리: 암호화 키는 소스 코드가 아닌 환경 변수 또는 Key Vault로 관리한다.

3. 암호화 - 전송

  • 적용 대상 : L3, L4 등급의 데이터 전송 시 필수 적용
  • 알고리즘: AES-256 (데이터 암호화) + RSA-2048 (키 교환) 하이브리드 방식
  • 구현 프로세스
    • [요청 시: Client → Server]
      1. 암호화 값 생성: Client 는 요청마다 새로운 일회용 대칭키(AES Key) 와 IV 를 생성한다.
      2. 데이터 암호화: 요청 본문을 생성한 Key와 IV로 암호화 한다.
      3. 키 암호화: 생성한 AES Key를 서버의 공개키로 암호화한다. (RSA)
      4. 패킹 구조 (Parsing 효율성을 위해 고정 길이 필드 우선 배치)
        • JSON 포맷 사용 금지: 페이로드의 효율성과 구조 은닉을 위해 바이트 배열로 직렬화하여 전송한다.
        • 구조: [ Encrypted AES Key (256 Bytes) ] + [ IV (16 Bytes) ] + [ Encrypted Body (Variable) ]
          • Body가 없는 단순 조회 요청이라도 Key와 IV는 필수 전송한다.(Body = 0 Byte)
        • 파싱 전략
          • Server는 수신된 바이너리 스트림의 첫 256바이트를 잘라 RSA 복호화하여 AES Key 획득
            • 다음 16바이트를 잘라 IV 획득
            • 나머지 바이트를 AES 복호화해 원본 Body 획득
    • [응답 시: Server → Client]
      1. 키 재사용 : 응답 시에는 키 교환을 하지 않고, 요청 패킷에서 획득한 AES Key를 그대로 재사용한다.
      2. 새로운 IV 생성: 보안 강화를 위해 응답을 위한 새로운 IV를 새로 생성한다.
      3. 패킹 구조 (Parsing 효율성을 위해 고정 길이 필드 우선 배치)
        • 구조: [ IV (16 Bytes) ] + [ Encrypted Body (Variable) ]
        • Client 는 자신이 보냈던 Key와 응답에 포함된 IV 를 사용해 복호화 한다.
      4. 복호화: Client는 자신이 갖고 있던 AES Key와 응답 패킷의 IV를 사용해 본문을 복호화한다.

3.3.9. 보안 감사 및 시큐어 코딩

  • 이미 정의된 네트워크/암호화 보안 외에 Application 계층에서 발생할 수 있는 취약점을 방어하고 추적성을 확보한다.

1. 보안 감사 로그

  • 저장소: SystemLog 테이블 (비동기 저장)
  • 기록 대상
    • 인증 실패: 로그인 n회 연속 실패, 비인가 프로젝트 키 접근 시도
    • 권한 위반: 401 Unauthorized, 403 Forbidden 응답 발생한 모든 요청
    • 중요한 변경: 관리자에 의한 데이터 변경, 데이터 삭제 행위
  • 로그 마스킹
    • 헤더 및 개인정보는 로그 저장 시 반드시 마스킹 처리를 한다.
    • 비밀번호, 암호화 키 원문은 어떠한 경우에도 로그 파일이나 콘솔에 평문으로 출력하지 않는다.

2. 시큐어 코딩

  • SQL Injection 방지: Raw Query 사용을 금지하며 반드시 EF Core(ORM) 메서드나 Parameterized Query 만 사용한다.
  • XSS(Cross-Site Scripting) 방지
    • BO 에서 HTML 입력을 허용해야 하는 경우 <script>, <iframe> 등 위험 태그를 제거 후 저장한다. (라이브러리: HtmlSanitizer)
    • 그 외 모든 입/출력 데이터는 기본적으로 HTML Encode 처리한다.

3.3.10. 웹 보안 및 헤더 정책

  • 3.3.1의 요청 규격과 달리 서버가 BO 등 브라우저 클라이언트 보호를 위해 응답시 자동으로 주입하는 보안 헤더 정책이다.
  • 미들웨어에서 일괄 처리해서 진행한다.
  1. HTTPS 강제 (HSTS)
    • 중간자 공격 방지 및 프로토콜 보안 강화
    • 설정: Strict-Transport-Security: max-age=31536000; includeSubDomains
  2. CORS
    • AllowAnyOrigin(*) 설정을 절대 금지한다.
    • 설정: 화이트리스트에 등록된 도메인(Admin URL, Partner URL)의 요청에 대해서만 허용 응답을 반환한다.
  3. 보안 헤더 자동 주입
    • 모든 API 응답에 아래 헤더를 강제로 포함시켜 브라우저의 보안 기능을 활성화한다.
헤더 명 방어 목적 설정 값
X-Frame-Options 클릭재킹(Clickjacking) 및 <iframe> 임베딩 차단 DENY
X-XSS-Protection 브라우저 내장 XSS 필터 강제 활성화 1; mode=block
X-Content-Type-Options MIME 타입 스니핑 차단 (악성 파일 실행 방지) nosniff
Content-Security-Policy 승인된 소스 외의 스크립트/리소스 로딩 차단 default-src 'self';

3.3.11. 핵심 기술 구현 전략

  • 주요 난제인 대량 트래픽 처리와 멀티 테넌시 격리를 위해 아래 4가지를 강제한다.

1. 멀티 테넌시 격리

  • Context Caching
    • 미들웨어는 X-API-KEY 검증 시 매번 DB를 조회하지 않고, Redis 캐시를 우선 조회하여 ProjectID를 식별한다.
  • Global Query Filter
    • 식별된 ProjectID를 EF Core의 Global Query Filter에 주입하여, 개발자의 실수로 인한 타 프로젝트 데이터 조회를 원천 차단한다.

2. 멱등성 보장

  • 중복 발송 방지
    • 네트워크 타임아웃으로 인한 재시도로 중복 실행을 막기 위해, 요청 진입 시 Redis에 X-Request-ID를 키로 조회한다.
  • Atomic Check
    • 이미 처리 중이거나 완료된 ID라면 작업을 즉시 폐기(Discard)한다

3. 대량 처리 최적화

  • Bulk Insert
    • 대량의 데이터 저장시 단건 Loop Insert를 금지하며, EF Core Bulk Extensions 를 사용하여 단일 커넥션으로 처리한다.
  • MQ QoS (Rabbit MQ)
    • Worker 서비스는 RabbitMQ의 Prefetch Count를 적정값으로 설정하여 메모리 과부하 없이 안정적으로 메시지를 소비해야 한다.

4. 데드 토큰 자동 정리

  • Worker 서비스가 발송 결과를 처리할 때 APNs/FCM 으로부터 Unregistered(앱 삭제) 또는 BadDeviceToken 응답을 받으면 즉시 해당 토큰을 DB에서 물리 삭제하여 비용 낭비를 방지한다.

3.3.12. API 버전 관리 (Versioning Strategy)

  • URI Versioning: 모든 API 엔드포인트는 URI 경로에 버전을 명시하여, 클라이언트의 강제 업데이트 없이도 구버전을 지원해야 한다.
  • 규칙: GET /api/v1/users (메이저 버전만 명시)
  • 정책: Breaking Change(필드 삭제, 타입 변경 등)가 발생할 경우에만 v2로 버전을 올리며, 기존 v1은 최소 N개월간 유지(Deprecated) 후 종료한다.

3.3.13. 시스템 회복 탄력성 (Resilience & Fault Tolerance)

  • 외부 시스템(DB, Redis, FCM, APNs) 장애가 전체 시스템 중단으로 전파되는 것을 막기 위해 Polly 라이브러리를 사용하여 아래 정책을 적용한다.
    • Retry (재시도): 일시적인 네트워크 오류 발생 시, Exponential Backoff(지수 대기: 2초, 4초, 8초...) 방식으로 최대 3회 재시도한다.
    • Circuit Breaker (차단기): 특정 외부 시스템(예: FCM)이 연속 N회 실패 시, 즉시 회로를 차단(Open)하여 대기 시간 없이 Fail-Fast 처리하고 시스템 리소스를 보호한다. (일정 시간 후 재접속 시도)
    • Timeout: 모든 외부 호출에는 반드시 제한 시간(Timeout)을 설정한다.

3.3.14. 상태 모니터링 (Health Checks)

  • 로드밸런서 및 모니터링 시스템이 서버의 생존 여부를 판단할 수 있도록 표준 엔드포인트를 제공한다.
  • Endpoint: GET /health
  • 검사 항목
    • Liveness: 서버 프로세스가 떠 있는가? (단순 Ping)
    • Readiness: DB, Redis, RabbitMQ와 정상적으로 연결되어 트래픽을 받을 준비가 되었는가?
  • 응답: 모든 연결 정상 시 200 OK, 하나라도 실패 시 503 Service Unavailable 반환.

3.4. 폴더 및 네임스페이스 구조

📂 SPMS.Solution 
├── 📂 SPMS.Domain 
│ ├── Entities (User.cs, Project.cs)
│ ├── Enums (UserRole.cs)
│ └── Exceptions (UserNotFoundException.cs)
│
├── 📂 SPMS.Application
│ ├── Interfaces (IUserService.cs, IUserRepository.cs)
│ ├── DTOs (Req, Res)
│ ├── Services (UserService.cs - 인터페이스 구현 아님, 로직 담당)
│ └── Mappers (MappingProfile.cs)
│
├── 📂 SPMS.Infrastructure
│ ├── Persistence (AppDbContext.cs)
│ ├── Repositories (UserRepository.cs - IUserRepository 구현)
│ └── ExternalServices (EmailService.cs) 
│
└── 📂 SPMS.API (ASP.NET Core Web API) 
	├── Controllers (UsersController.cs)
	├── Middlewares 
	└── Program.cs