온라인쇼핑몰 - 주문처리 프로세스

 

주요 Actor와 Object 설명

Actors (액터)

  1. 고객 (Customer)
    • 주문 프로세스를 시작하는 주체
    • 상품을 선택하고 결제를 진행하는 사용자

Objects (객체/시스템)

  1. 웹 인터페이스 (Web UI)
    • 고객과 시스템 간의 상호작용을 담당하는 프론트엔드
    • 사용자 입력을 받고 시스템 응답을 화면에 표시
  2. 주문 시스템 (Order System)
    • 주문 프로세스 전체를 조율하는 핵심 시스템
    • 다른 시스템들과의 상호작용을 관리하는 중앙 제어 역할
  3. 재고 시스템 (Inventory System)
    • 상품의 재고 상태를 관리
    • 재고 확인 및 차감 기능 제공
  4. 결제 게이트웨이 (Payment Gateway)
    • 외부 결제 서비스와의 연동을 담당
    • 신용카드, 계좌이체 등 다양한 결제 수단 처리
  5. 주문 데이터베이스 (Order Database)
    • 주문 정보를 저장하고 관리
    • 주문 상태 추적 및 이력 관리
  6. 배송 시스템 (Shipping System)
    • 주문 상품의 배송을 담당
    • 배송업체 연동 및 배송 정보 제공
  7. 이메일 서비스 (Email Service)
    • 고객에게 주문 확인, 배송 정보 등을 이메일로 전송
    • 알림 서비스 담당

프로세스 흐름 설명

정상 처리 흐름:

  1. 고객이 상품을 장바구니에 담고 주문을 시작
  2. 시스템이 재고를 확인하여 주문 가능 여부 검증
  3. 결제 처리를 통해 주문을 확정
  4. 재고 차감 및 배송 준비
  5. 고객에게 주문 확인 이메일 발송

예외 처리:

  • 재고 부족 시: 고객에게 알림 후 주문 중단
  • 결제 실패 시: 임시 주문 취소 후 고객에게 알림

이 다이어그램은 온라인 쇼핑몰의 주문 처리 과정에서 발생하는 주요 상호작용과 예외 상황을 포괄적으로 보여줍니다.

 

 

 

 

클라우드 네이티브 아키텍처 설계 가이드


         
단계 항목 주요활동 필요기술/도구 주의사항
1. 사전 준비 및 평가 현재 상황 분석 • 기존 아키텍처 분석<br>• 비즈니스 요구사항 정의<br>• 팀 역량 평가<br>• 예산/일정 계획 • 아키텍처 문서화 도구<br>• 설문조사/인터뷰<br>• 프로젝트 관리 도구 • 현실적인 목표 설정<br>• 조직 변화 관리 준비
  목표 설정 • SLA/SLO 지표 정의<br>• 마이그레이션 전략 수립 • 모니터링 도구<br>• 로드맵 관리 도구 • 측정 가능한 목표<br>• 점진적 전환 고려
2. 아키텍처 설계 원칙 도메인 기반 설계 • 서비스 경계 정의<br>• API 설계<br>• 데이터 소유권 분리 • DDD 방법론<br>• API 설계 도구<br>• 문서화 플랫폼 • 비즈니스 도메인 이해<br>• 서비스 크기 적정화
  12-Factor App • 코드베이스 관리<br>• 환경별 설정 분리<br>• 상태 없는 프로세스 • Git<br>• 환경 변수 관리<br>• 설정 관리 도구 • 원칙 준수 체크리스트<br>• 개발팀 교육 필요
3. 핵심 기술 스택 컨테이너 플랫폼 • 컨테이너화<br>• 오케스트레이션<br>• 패키지 관리 • Docker<br>• Kubernetes<br>• Helm • 학습 곡선 고려<br>• 운영 복잡성 증가
  서비스 메시 • 서비스 간 통신 관리<br>• 보안 정책 적용<br>• 트래픽 제어 • Istio<br>• Linkerd<br>• Consul Connect • 초기 복잡성<br>• 성능 오버헤드
4. 인프라 및 플랫폼 클라우드 서비스 • 클라우드 제공자 선택<br>• 관리형 서비스 활용<br>• 멀티/하이브리드 클라우드 • AWS/GCP/Azure<br>• EKS/GKE/AKS<br>• RDS/Cloud SQL • 벤더 락인 위험<br>• 비용 관리
  IaC • 인프라 코드화<br>• GitOps 워크플로우<br>• 버전 관리 • Terraform<br>• CloudFormation<br>• Pulumi • 코드 품질 관리<br>• 상태 파일 보안
5. 보안 설계 Zero Trust 모델 • mTLS 구현<br>• RBAC 설정<br>• 시크릿 관리 • Istio/Linkerd<br>• K8s RBAC<br>• Vault/K8s Secrets • 초기 설정 복잡성<br>• 성능 영향 고려
  컨테이너 보안 • 이미지 스캐닝<br>• 런타임 보안<br>• 최소 권한 적용 • Twistlock/Aqua<br>• Falco<br>• Pod Security Standards • 지속적인 취약점 관리<br>• 개발 워크플로우 통합
6. 관찰가능성 3 Pillars • 로깅 시스템 구축<br>• 모니터링 설정<br>• 분산 트레이싱 • ELK Stack<br>• Prometheus/Grafana<br>• Jaeger/Zipkin • 데이터 볼륨 관리<br>• 노이즈 필터링
  알림 및 대시보드 • SLI/SLO 설정<br>• 비즈니스 메트릭<br>• 알림 정책 • Grafana<br>• AlertManager<br>• PagerDuty • 알림 피로도 방지<br>• 의미있는 메트릭 선별
7. CI/CD 파이프라인 자동화된 배포 • 파이프라인 구축<br>• 자동화된 테스트<br>• 배포 전략 실행 • Jenkins/GitLab CI<br>• GitHub Actions<br>• ArgoCD/Flux • 테스트 커버리지<br>• 배포 속도와 안정성 균형
8. 데이터 관리 분산 데이터 관리 • DB per Service<br>• 이벤트 소싱<br>• 데이터 일관성 관리 • 적절한 DB 선택<br>• Kafka/Event Bus<br>• Saga Pattern • 데이터 정합성<br>• 복잡성 증가
  백업 및 DR • 자동화된 백업<br>• 멀티 리전 복제<br>• RTO/RPO 계획 • 클라우드 백업 서비스<br>• 복제 도구 • 비용과 성능 트레이드오프<br>• 정기적인 복구 테스트
9. 성능 최적화 캐싱 전략 • 다층 캐싱<br>• CDN 활용<br>• 캐시 무효화 • Redis/Memcached<br>• CloudFront/CloudFlare<br>• 캐시 라이브러리 • 캐시 일관성<br>• 메모리 관리
  비동기 처리 • 메시지 큐 구축<br>• 이벤트 기반 통신<br>• 배치 처리 • Kafka/RabbitMQ<br>• AWS SQS/SNS<br>• 스케줄링 도구 • 메시지 순서<br>• 장애 처리
10. 거버넌스 및 운영 API 관리 • API 버전 관리<br>• 문서화<br>• 개발자 포털 • API Gateway<br>• Swagger/OpenAPI<br>• 개발자 포털 도구 • API 호환성<br>• 문서 최신성 유지
  비용 최적화 • 리소스 모니터링<br>• 자동 스케일링<br>• 예약 인스턴스 • 클라우드 비용 관리 도구<br>• HPA/VPA<br>• 예약 인스턴스 관리 • 과도한 프로비저닝<br>• 리소스 낭비 방지
11. 실행 계획 Phase 1 인프라 준비 및 기반 도구 구축 • 기본 K8s 클러스터<br>• CI/CD 파이프라인<br>• 모니터링 기본 설정 • 안정성 우선<br>• 팀 교육 병행
  Phase 2 파일럿 서비스 마이그레이션 • 단순한 서비스 선택<br>• A/B 테스트 환경<br>• 롤백 계획 • 리스크가 낮은 서비스<br>• 충분한 테스트
  Phase 3 점진적 서비스 확장 • 의존성 관리<br>• 데이터 마이그레이션<br>• 성능 최적화 • 단계적 확장<br>• 지속적인 모니터링
  Phase 4 최적화 및 고도화 • 성능 튜닝<br>• 고급 기능 적용<br>• 운영 자동화 • 지속적인 개선<br>• 팀 역량 고도화

성공 요인

영역핵심 요소권장사항
조직 • 팀 구조 개편<br>• 문화 변화<br>• 지속적 학습 • 크로스 펑셔널 팀 구성<br>• DevOps 문화 정착<br>• 정기적인 회고 및 개선
기술 • 단계적 적용<br>• 표준화<br>• 자동화 • PoC를 통한 검증<br>• 내부 표준 수립<br>• 모든 영역의 자동화
운영 • 모니터링<br>• 장애 대응<br>• 성능 관리 • 사전 예방적 모니터링<br>• 신속한 장애 복구<br>• 지속적인 성능 최적화

 

클라우드 네이티브 아키텍처 설계 가이드

1. 사전 준비 및 평가

현재 상황 분석

  • 기존 애플리케이션의 아키텍처와 기술 스택 파악
  • 비즈니스 요구사항과 성능 목표 정의
  • 팀의 기술 역량과 조직 준비도 평가
  • 예산과 일정 계획 수립

목표 설정

  • 확장성, 가용성, 성능 등 구체적인 목표 지표 설정
  • 마이그레이션 전략 결정 (Big Bang vs 점진적 전환)

2. 아키텍처 설계 원칙

도메인 기반 설계

  • 비즈니스 도메인에 따른 서비스 경계 정의
  • 각 마이크로서비스의 책임과 데이터 소유권 분리
  • API 우선 설계로 서비스 간 계약 명확화

12-Factor App 원칙 적용

  • 코드베이스, 의존성, 설정, 백엔드 서비스 등 12가지 원칙 준수
  • 환경별 설정 분리와 상태 없는(stateless) 프로세스 설계

3. 핵심 기술 스택 선택

컨테이너 플랫폼

  • Docker for 컨테이너화
  • Kubernetes for 오케스트레이션
  • Helm for 패키지 관리

서비스 메시

  • Istio, Linkerd, 또는 Consul Connect
  • 서비스 간 통신 보안, 트래픽 관리, 관찰가능성

데이터베이스

  • 각 서비스별 적합한 데이터베이스 선택
  • Database per Service 패턴 적용
  • 이벤트 소싱이나 CQRS 패턴 고려

4. 인프라 및 플랫폼

클라우드 서비스 선택

  • AWS, GCP, Azure 등 퍼블릭 클라우드 또는 하이브리드
  • 관리형 서비스 활용 (RDS, EKS, GKE 등)

Infrastructure as Code

  • Terraform, CloudFormation, Pulumi 등으로 인프라 코드화
  • GitOps 워크플로우 구현

네트워킹

  • Service Discovery 메커니즘
  • Load Balancing 전략
  • API Gateway 구성

5. 보안 설계

Zero Trust 보안 모델

  • 서비스 간 mTLS 통신
  • 역할 기반 접근 제어(RBAC)
  • 시크릿 관리 시스템 (Vault, K8s Secrets)

컨테이너 보안

  • 이미지 스캐닝과 취약점 관리
  • 최소 권한 원칙 적용
  • 런타임 보안 모니터링

6. 관찰가능성 구현

3 Pillars of Observability

  • Logging: ELK Stack, Fluentd
  • Monitoring: Prometheus + Grafana
  • Tracing: Jaeger, Zipkin

알림 및 대시보드

  • SLI/SLO 기반 모니터링
  • 비즈니스 메트릭과 기술 메트릭 연결

7. CI/CD 파이프라인

자동화된 배포

  • Git 기반 소스 코드 관리
  • 자동화된 테스트 (단위, 통합, E2E)
  • Blue-Green 또는 Canary 배포 전략

도구 선택

  • Jenkins, GitLab CI, GitHub Actions
  • ArgoCD, Flux for GitOps

8. 데이터 관리 전략

분산 데이터 관리

  • 각 서비스의 데이터 독립성 보장
  • 이벤트 중심 아키텍처 구현
  • 데이터 일관성과 트랜잭션 관리 (Saga 패턴)

백업 및 재해 복구

  • 자동화된 백업 전략
  • 멀티 리전 데이터 복제
  • RTO/RPO 목표 설정

9. 성능 최적화

캐싱 전략

  • 애플리케이션, 데이터베이스, CDN 레벨 캐싱
  • Redis, Memcached 활용

비동기 처리

  • 메시지 큐(Kafka, RabbitMQ) 활용
  • 이벤트 기반 비동기 통신

10. 거버넌스 및 운영

API 관리

  • API 버전 관리 전략
  • API 문서화 및 개발자 포털

비용 최적화

  • 리소스 사용량 모니터링
  • 자동 스케일링 정책 최적화
  • 예약 인스턴스 활용

팀 조직

  • DevOps 문화 정착
  • 크로스 펑셔널 팀 구성
  • 지속적인 학습과 개선

11. 단계별 실행 계획

Phase 1: 인프라 준비 및 기반 도구 구축 Phase 2: 파일럿 서비스 마이그레이션 Phase 3: 점진적 서비스 확장 Phase 4: 최적화 및 고도화

각 단계마다 성공 지표를 설정하고, 피드백을 통해 지속적으로 개선해 나가는 것이 중요합니다. 처음부터 완벽한 클라우드 네이티브 시스템을 구축하기보다는, 점진적으로 성숙도를 높여가는 접근이 현실적입니다.

응집도 유형별 특징 요약

응집도 강도 순서 (강함 → 약함)

기능적 > 순차적 > 교환적 > 절차적 > 시간적 > 논리적 > 우연적


1. 논리적 응집도 (Logical Cohesion)

특징:

  • 비슷한 성격의 처리 요소들이 한 모듈에서 조건문으로 선택되어 수행
  • 관련된 기능들을 논리적으로 그룹화

장점: 관련 기능들이 한 곳에 모여있어 찾기 쉬움
단점: 하나의 함수가 여러 역할을 담당하여 복잡해질 수 있음


2. 우연적 응집도 (Coincidental Cohesion)

특징:

  • 서로 전혀 관련 없는 요소들이 우연히 한 모듈에 위치
  • 가장 낮은 수준의 응집도

단점:

  • 유지보수가 매우 어려움
  • 모듈의 목적이 불분명
  • 재사용성이 떨어짐

3. 절차적 응집도 (Procedural Cohesion)

특징:

  • 모듈 안의 구성요소들이 기능을 순차적으로 수행
  • 특정 절차나 순서가 중요한 작업들의 집합

예시: 사용자 등록, 시스템 설치 과정 등


4. 순차적 응집도 (Sequential Cohesion)

특징:

  • 한 활동의 출력이 다음 활동의 입력으로 사용됨
  • 데이터 흐름이 연속적으로 이어지는 파이프라인 형태
  • 절차적 응집도보다 강한 결합

예시: 데이터 처리 파이프라인, 컴파일러의 각 단계


5. 시간적 응집도 (Temporal Cohesion)

특징:

  • 특정 시간에 처리되어야 하는 기능들을 모은 형태
  • 실행 시점이 중요한 작업들의 집합

예시:

  • 시스템 초기화 (initialize)
  • 시스템 종료 (cleanup)
  • 예외 처리 (error handling)

실무 적용 가이드

  1. 논리적 응집도: 관련 기능들을 별도 클래스로 분리하는 것이 좋음
  2. 우연적 응집도: 반드시 리팩토링 필요 - 기능별로 모듈 분리
  3. 절차적 응집도: 템플릿 메서드 패턴 적용 고려
  4. 순차적 응집도: 파이프라인 패턴이나 체인 패턴 활용
  5. 시간적 응집도: 이벤트 기반 아키텍처나 생명주기 관리 패턴 고려

가장 이상적인 것은 기능적 응집도(하나의 모듈이 하나의 기능만 수행)이지만, 위 5가지 유형도 상황에 따라 적절히 사용될 수 있습니다.

 


// ========== 1. 논리적 응집도 (Logical Cohesion) ==========
// 특징: 비슷한 성격의 처리 요소들이 조건문으로 선택되어 수행됨
// 장점: 관련 기능들이 한 곳에 모여있음
// 단점: 하나의 함수가 여러 역할을 담당하여 복잡해질 수 있음

class LogicalCohesionExample {
    // 파일 처리 관련 기능들을 조건에 따라 선택적으로 수행
    public void fileProcessor(String operation, String filename, String content) {
        if (operation.equals("read")) {
            System.out.println("파일 읽기: " + filename);
            // 파일 읽기 로직
        } else if (operation.equals("write")) {
            System.out.println("파일 쓰기: " + filename + " 내용: " + content);
            // 파일 쓰기 로직
        } else if (operation.equals("delete")) {
            System.out.println("파일 삭제: " + filename);
            // 파일 삭제 로직
        } else if (operation.equals("copy")) {
            System.out.println("파일 복사: " + filename);
            // 파일 복사 로직
        }
    }
    
    // 수학 연산을 조건에 따라 선택적으로 수행
    public double mathOperations(String operator, double a, double b) {
        if (operator.equals("add")) {
            return a + b;
        } else if (operator.equals("subtract")) {
            return a - b;
        } else if (operator.equals("multiply")) {
            return a * b;
        } else if (operator.equals("divide")) {
            return a / b;
        }
        return 0;
    }
}

// ========== 2. 우연적 응집도 (Coincidental Cohesion) ==========
// 특징: 서로 관련 없는 요소들이 우연히 한 모듈에 위치
// 단점: 가장 낮은 수준의 응집도, 유지보수가 어려움

class CoincidentalCohesionExample {
    private String userName;
    private double temperature;
    private int[] numbers;
    
    // 전혀 관련 없는 기능들이 하나의 메서드에 혼재
    public void mixedFunctions() {
        // 사용자 이름 출력
        System.out.println("사용자: " + userName);
        
        // 온도를 화씨로 변환
        double fahrenheit = (temperature * 9/5) + 32;
        System.out.println("화씨 온도: " + fahrenheit);
        
        // 배열 정렬
        java.util.Arrays.sort(numbers);
        System.out.println("정렬된 배열: " + java.util.Arrays.toString(numbers));
        
        // 현재 시간 출력
        System.out.println("현재 시간: " + new java.util.Date());
    }
    
    // 관련 없는 여러 작업을 한 번에 수행
    public void doEverything(String name, int age, double salary, String email) {
        // 이름 유효성 검사
        if (name != null && name.length() > 0) {
            this.userName = name;
        }
        
        // 나이로 세대 분류
        String generation = age < 30 ? "MZ세대" : "기성세대";
        System.out.println(generation);
        
        // 급여 세금 계산
        double tax = salary * 0.22;
        System.out.println("세금: " + tax);
        
        // 이메일 도메인 추출
        String domain = email.substring(email.indexOf('@') + 1);
        System.out.println("이메일 도메인: " + domain);
    }
}

// ========== 3. 절차적 응집도 (Procedural Cohesion) ==========
// 특징: 모듈 안의 구성요소들이 기능을 순차적으로 수행
// 특정 순서로 실행되어야 하는 작업들의 집합

class ProceduralCohesionExample {
    // 사용자 등록 절차를 순서대로 수행
    public boolean registerUser(String username, String email, String password) {
        // 1단계: 입력 유효성 검사
        if (!validateInput(username, email, password)) {
            System.out.println("입력 검증 실패");
            return false;
        }
        
        // 2단계: 중복 사용자 확인
        if (checkDuplicateUser(username, email)) {
            System.out.println("중복 사용자 존재");
            return false;
        }
        
        // 3단계: 비밀번호 암호화
        String encryptedPassword = encryptPassword(password);
        
        // 4단계: 데이터베이스에 저장
        saveToDatabase(username, email, encryptedPassword);
        
        // 5단계: 환영 이메일 발송
        sendWelcomeEmail(email);
        
        System.out.println("사용자 등록 완료");
        return true;
    }
    
    private boolean validateInput(String username, String email, String password) {
        return username != null && email.contains("@") && password.length() >= 8;
    }
    
    private boolean checkDuplicateUser(String username, String email) {
        // 중복 검사 로직 (예시)
        return false;
    }
    
    private String encryptPassword(String password) {
        return "encrypted_" + password; // 단순 예시
    }
    
    private void saveToDatabase(String username, String email, String password) {
        System.out.println("데이터베이스에 저장: " + username);
    }
    
    private void sendWelcomeEmail(String email) {
        System.out.println("환영 이메일 발송: " + email);
    }
}

// ========== 4. 순차적 응집도 (Sequential Cohesion) ==========
// 특징: 한 활동의 출력이 다음 활동의 입력으로 사용됨
// 데이터 흐름이 연속적으로 이어짐

class SequentialCohesionExample {
    // 데이터 처리 파이프라인: 각 단계의 출력이 다음 단계의 입력이 됨
    public String processData(String rawData) {
        // 1단계: 원본 데이터를 정제된 데이터로 변환
        String cleanedData = cleanData(rawData);
        
        // 2단계: 정제된 데이터를 검증된 데이터로 변환
        String validatedData = validateData(cleanedData);
        
        // 3단계: 검증된 데이터를 변환된 데이터로 변환
        String transformedData = transformData(validatedData);
        
        // 4단계: 변환된 데이터를 포맷된 데이터로 변환
        String formattedData = formatData(transformedData);
        
        return formattedData;
    }
    
    private String cleanData(String rawData) {
        System.out.println("데이터 정제 중...");
        return rawData.trim().toLowerCase();
    }
    
    private String validateData(String cleanedData) {
        System.out.println("데이터 검증 중...");
        return cleanedData.matches("^[a-z0-9]+$") ? cleanedData : "invalid";
    }
    
    private String transformData(String validatedData) {
        System.out.println("데이터 변환 중...");
        return validatedData.toUpperCase();
    }
    
    private String formatData(String transformedData) {
        System.out.println("데이터 포맷팅 중...");
        return "[" + transformedData + "]";
    }
    
    // 수학적 계산 파이프라인
    public double calculateFinalResult(double input) {
        // 1단계: 제곱
        double squared = square(input);
        
        // 2단계: 제곱 결과에 상수 추가
        double added = addConstant(squared);
        
        // 3단계: 추가된 결과를 정규화
        double normalized = normalize(added);
        
        // 4단계: 정규화된 결과를 반올림
        double rounded = roundResult(normalized);
        
        return rounded;
    }
    
    private double square(double x) {
        return x * x;
    }
    
    private double addConstant(double x) {
        return x + 10.5;
    }
    
    private double normalize(double x) {
        return x / 100.0;
    }
    
    private double roundResult(double x) {
        return Math.round(x * 100.0) / 100.0;
    }
}

// ========== 5. 시간적 응집도 (Temporal Cohesion) ==========
// 특징: 특정 시간에 처리되어야 하는 기능들을 모은 형태
// 초기화, 종료, 예외 처리 등에서 주로 나타남

class TemporalCohesionExample {
    private java.sql.Connection dbConnection;
    private java.util.logging.Logger logger;
    private java.util.Properties config;
    
    // 시스템 초기화 시점에 실행되는 여러 작업들
    public void initializeSystem() {
        System.out.println("시스템 초기화 시작...");
        
        // 로거 초기화
        initializeLogger();
        
        // 설정 파일 로드
        loadConfiguration();
        
        // 데이터베이스 연결 설정
        setupDatabaseConnection();
        
        // 메모리 풀 초기화
        initializeMemoryPool();
        
        // 캐시 시스템 초기화
        initializeCacheSystem();
        
        System.out.println("시스템 초기화 완료");
    }
    
    // 시스템 종료 시점에 실행되는 여러 작업들
    public void shutdownSystem() {
        System.out.println("시스템 종료 시작...");
        
        // 진행 중인 작업 완료 대기
        waitForPendingTasks();
        
        // 캐시 데이터 저장
        saveCacheData();
        
        // 데이터베이스 연결 종료
        closeDatabaseConnection();
        
        // 로그 파일 닫기
        closeLogFiles();
        
        // 임시 파일 정리
        cleanupTempFiles();
        
        System.out.println("시스템 종료 완료");
    }
    
    // 예외 발생 시 실행되는 여러 작업들
    public void handleSystemError(Exception e) {
        System.out.println("시스템 오류 처리 시작...");
        
        // 오류 로그 기록
        logError(e);
        
        // 현재 상태 백업
        backupCurrentState();
        
        // 사용자에게 오류 알림
        notifyUser("시스템 오류가 발생했습니다");
        
        // 관리자에게 이메일 전송
        sendErrorNotificationToAdmin(e);
        
        // 시스템 상태 초기화
        resetSystemState();
        
        System.out.println("시스템 오류 처리 완료");
    }
    
    // 초기화 관련 메서드들
    private void initializeLogger() {
        System.out.println("로거 초기화");
        this.logger = java.util.logging.Logger.getLogger("SystemLogger");
    }
    
    private void loadConfiguration() {
        System.out.println("설정 파일 로드");
        this.config = new java.util.Properties();
    }
    
    private void setupDatabaseConnection() {
        System.out.println("데이터베이스 연결 설정");
        // DB 연결 로직
    }
    
    private void initializeMemoryPool() {
        System.out.println("메모리 풀 초기화");
    }
    
    private void initializeCacheSystem() {
        System.out.println("캐시 시스템 초기화");
    }
    
    // 종료 관련 메서드들
    private void waitForPendingTasks() {
        System.out.println("진행 중인 작업 대기");
    }
    
    private void saveCacheData() {
        System.out.println("캐시 데이터 저장");
    }
    
    private void closeDatabaseConnection() {
        System.out.println("데이터베이스 연결 종료");
    }
    
    private void closeLogFiles() {
        System.out.println("로그 파일 닫기");
    }
    
    private void cleanupTempFiles() {
        System.out.println("임시 파일 정리");
    }
    
    // 오류 처리 관련 메서드들
    private void logError(Exception e) {
        System.out.println("오류 로그 기록: " + e.getMessage());
    }
    
    private void backupCurrentState() {
        System.out.println("현재 상태 백업");
    }
    
    private void notifyUser(String message) {
        System.out.println("사용자 알림: " + message);
    }
    
    private void sendErrorNotificationToAdmin(Exception e) {
        System.out.println("관리자에게 오류 알림 전송");
    }
    
    private void resetSystemState() {
        System.out.println("시스템 상태 초기화");
    }
}

// ========== 메인 클래스: 각 응집도 유형 실행 예시 ==========
public class CohesionExamples {
    public static void main(String[] args) {
        System.out.println("=== 응집도 유형별 예시 실행 ===\n");
        
        // 1. 논리적 응집도 실행
        System.out.println("1. 논리적 응집도 예시:");
        LogicalCohesionExample logical = new LogicalCohesionExample();
        logical.fileProcessor("read", "test.txt", null);
        System.out.println("결과: " + logical.mathOperations("add", 10, 5));
        System.out.println();
        
        // 2. 우연적 응집도 실행
        System.out.println("2. 우연적 응집도 예시:");
        CoincidentalCohesionExample coincidental = new CoincidentalCohesionExample();
        coincidental.mixedFunctions();
        System.out.println();
        
        // 3. 절차적 응집도 실행
        System.out.println("3. 절차적 응집도 예시:");
        ProceduralCohesionExample procedural = new ProceduralCohesionExample();
        procedural.registerUser("john_doe", "john@example.com", "password123");
        System.out.println();
        
        // 4. 순차적 응집도 실행
        System.out.println("4. 순차적 응집도 예시:");
        SequentialCohesionExample sequential = new SequentialCohesionExample();
        String result = sequential.processData("  HELLO World 123  ");
        System.out.println("최종 결과: " + result);
        System.out.println("수학 계산 결과: " + sequential.calculateFinalResult(5.0));
        System.out.println();
        
        // 5. 시간적 응집도 실행
        System.out.println("5. 시간적 응집도 예시:");
        TemporalCohesionExample temporal = new TemporalCohesionExample();
        temporal.initializeSystem();
        System.out.println();
        temporal.shutdownSystem();
    }
}

 

 

 

 

유스케이스 다이어그램 (Use Case Diagram)

- 시스템이 어떤 기능을 제공하는지, 그리고 누가 그 기능을 사용하는지 전체적인 그림을 보여주는 다이어그램

- 기능(use case)들과 이 기능을 사용하는 사용자(actor)를 그림으로 나타낸 것

- 무엇을 하는지에 초점

 

시퀀스 다이어그램 (Sequence Diagram)

- 시스템의 여러 객체들이 어떤 시간 순서로 상호작용하는지를 보여주는 것

- 실례: 온라인 뱅킹에서 계좌 이체를 하는 과정을 예로 들어 볼게.

  • 사용자뱅킹 앱에 로그인한다.
  • 뱅킹 앱인증 서버에 사용자 인증을 요청한다.
  • 인증이 성공하면 뱅킹 앱계좌 서버에 이체 정보를 전송한다.
  • 계좌 서버는 송금 계좌에서 금액을 차감하고, 수금 계좌에 금액을 더한다.
  • 계좌 서버뱅킹 앱에 이체 결과를 전송한다.

- 어떻게 하는지에 초점

 

클래스 다이어그램 (Class Diagram)

-클래스 다이어그램은 시스템의 정적인 구조를 보여주는 다이어그램.

 여기서 '정적'이라는 말은 시스템이 동작하는 과정이 아니라, 시스템을 구성하는 요소와 그 요소들이 서로 어떤 관계를 맺고 있는지를 의미

- 주요 구성요소는 (클래스) 와 (관계)

- 클래스 다이어그램은 시스템의 구조를 명확하게 보여주기 때문에, 여러 개발자가 협업할 때 서로의 코드가 어떻게 연결되어 있는지 이해하는 데 큰 도움이 됨

 

컴포넌트 다이어그램 (Component Diagram)

-컴포넌트 다이어그램은 시스템의 물리적인 구조를 보여주는 그림이야. 여기서 '컴포넌트'는 시스템을 이루는 작고 독립적인 소프트웨어 조각들을 말해. 마치 레고 블록처럼, 미리 만들어진 기능들을 합쳐서 하나의 큰 시스템을 만드는 거

쉽게 비유하자면, 자동차를 조립한다고 상상해 보면...

엔진, 바퀴, 핸들 등 각 부품이 독립적으로 만들어져 있고, 이 부품들을 조립해서 자동차를 완성하지?

이 부품 하나하나가 바로 소프트웨어의 컴포넌트와 비슷함

컴포넌트 다이어그램은 이런 컴포넌트들이 어떻게 연결되어 서로 기능을 주고받는지를 보여줘.

각 컴포넌트가 어떤 기능을 **제공(Provided Interface)**하고, 어떤 기능을 다른 컴포넌트로부터 **요구(Required Interface)**하는지 나타냄

 

배치 다이어그램 (Deployment Diagram)

-컴포넌트 다이어그램이 '어떤 부품들이 있는지'를 보여줬다면, 배치 다이어그램은 '그 부품들이 어디에 놓여 있는지'를 보여줘.

다시 말해, 컴포넌트들이 실제로 어떤 **하드웨어 환경(서버, PC 등)**에 배포되어 실행되는지를 나타내는 거야.

이해하기 쉽게 비유해볼까? 집을 지을 때, 설계도를 보면서 "주방에는 냉장고를, 거실에는 TV를 놓자"라고 결정하잖아?

배치 다이어그램은 바로 이 배치 계획과 같아.

이 다이어그램은 시스템이 물리적으로 어떻게 구성되어 있는지를 보여줘서, 시스템의 실제 운영 환경을 이해하는 데 아주 중요해. 시스템을 확장하거나 문제가 생겼을 때, 어떤 서버에 어떤 기능이 올라가 있는지 한눈에 파악할 수 있지.

 

 

 

Apache Kafka 기반 비동기 아키텍처 개요

Kafka는 분산 스트리밍 플랫폼으로, 높은 처리량과 낮은 지연 시간을 제공하는 이벤트 스트리밍 시스템입니다. 실시간 데이터 파이프라인과 스트리밍 애플리케이션 구축에 특화되어 있습니다.

핵심 구성 요소

1. Kafka Cluster 구조

**브로커(Broker)**는 Kafka 서버 인스턴스로, 메시지를 저장하고 클라이언트 요청을 처리합니다. 일반적으로 3개 이상의 브로커로 클러스터를 구성하여 고가용성을 확보합니다.

**토픽(Topic)**은 메시지를 분류하는 논리적 단위입니다. 예를 들어 user-events, order-processing, notification-requests 등으로 구분합니다.

**파티션(Partition)**은 토픽을 물리적으로 분할한 단위로, 병렬 처리와 확장성을 제공합니다. 파티션 수가 많을수록 더 많은 컨슈머가 동시에 처리할 수 있습니다.

2. ZooKeeper/KRaft

ZooKeeper는 클러스터 메타데이터 관리와 리더 선출을 담당합니다. Kafka 2.8 이후부터는 KRaft 모드로 ZooKeeper 의존성을 제거할 수 있습니다.

비동기 처리 패턴

1. Event Sourcing Pattern

 
 
User Action → Event Producer → Kafka Topic → Event Consumer → Business Logic

모든 상태 변경을 이벤트로 기록하여 시스템 상태를 재구성할 수 있습니다. 주문 생성, 결제 처리, 배송 시작 등 각 단계를 이벤트로 관리합니다.

2. CQRS (Command Query Responsibility Segregation)

명령(Command)과 조회(Query)를 분리하여, 쓰기 작업은 Kafka를 통해 비동기로 처리하고, 읽기 작업은 최적화된 별도 저장소에서 처리합니다.

3. Saga Pattern

분산 트랜잭션을 여러 개의 로컬 트랜잭션으로 분해하여 Kafka를 통해 조정합니다. 각 서비스는 자신의 작업을 완료한 후 다음 단계의 이벤트를 발행합니다.

Producer 설계 고려사항

1. 메시지 전송 보장

acks 설정을 통해 메시지 전송 신뢰성을 조절합니다. acks=all로 설정하면 모든 복제본이 메시지를 받을 때까지 대기하여 최고 수준의 내구성을 보장합니다.

배치 처리를 통해 성능을 최적화합니다. batch.size와 linger.ms 설정으로 배치 크기와 대기 시간을 조절합니다.

2. 파티셔닝 전략

메시지 키를 기반으로 파티션을 결정하여 관련된 이벤트들이 순서대로 처리되도록 합니다. 예를 들어 사용자 ID를 키로 사용하면 동일 사용자의 이벤트가 같은 파티션에서 순차 처리됩니다.

Consumer 설계 고려사항

1. Consumer Group

여러 컨슈머가 하나의 그룹을 형성하여 토픽의 파티션을 분산 처리합니다. 컨슈머가 추가되면 파티션이 재분배되어 자동으로 부하가 분산됩니다.

2. 오프셋 관리

자동 커밋수동 커밋 중 선택할 수 있습니다. 중요한 비즈니스 로직의 경우 수동 커밋을 사용하여 처리 완료 후에만 오프셋을 커밋합니다.

3. 재시도 및 에러 처리

처리 실패 시 재시도 토픽으로 메시지를 전송하거나, Dead Letter Topic으로 격리하여 별도 처리합니다.

Kafka Streams를 활용한 스트림 처리

1. 실시간 데이터 변환

입력 스트림을 실시간으로 변환하여 다른 토픽으로 출력합니다. 예를 들어 원시 이벤트를 집계하여 통계 정보를 생성합니다.

2. 윈도우 연산

시간 기반 윈도우를 사용하여 특정 시간 구간의 데이터를 집계합니다. 1분간의 주문 수량, 5분간의 평균 응답 시간 등을 계산할 수 있습니다.

모니터링 및 운영

1. 주요 메트릭

  • 처리량: 초당 메시지 수 (messages/sec)
  • 지연 시간: 프로듀서에서 컨슈머까지의 end-to-end latency
  • 컨슈머 랙: 아직 처리하지 못한 메시지 수
  • 파티션 분산: 각 파티션별 부하 분산 상태

2. 모니터링 도구

JMX 메트릭을 활용하여 Prometheus + Grafana로 대시보드를 구성하거나, Confluent Control Center, Kafka Manager 등 전용 도구를 사용합니다.

확장성 및 성능 최적화

1. 수평적 확장

브로커 추가를 통한 클러스터 확장과 파티션 증가를 통한 병렬 처리 향상이 가능합니다. 컨슈머 인스턴스를 늘려 처리 능력을 향상시킬 수 있습니다.

2. 성능 튜닝

  • 압축 설정: gzip, snappy, lz4 등을 사용하여 네트워크 대역폭 절약
  • 복제 팩터: 내구성과 성능의 균형점 찾기 (일반적으로 3)
  • 배치 크기 최적화: 지연 시간과 처리량 사이의 트레이드오프 조절

이러한 Kafka 기반 아키텍처를 통해 대용량 실시간 데이터 처리가 가능하며, 마이크로서비스 간 느슨한 결합과 높은 확장성을 달성할 수 있습니다.

 

 

 

출처: https://modulabs.co.kr/blog/mcp

 

MCP란 무엇일까요?

MCP?

안녕하세요! 요즘 MCP에 대한 이야기는 많은데, 정확히 뭔지 궁금하셨죠? 우리가 일상적으로 쓰는 AI 서비스들, 예를 들면 ChatGPT, Claude, Gemini 같은 것들은 각자 좀 다른 특징이 있어요. Claude는 코딩이랑 글쓰기에 강하고, Gemini는 이미지나 영상 같은 멀티모달에 더 특화되어 있죠. ChatGPT는 뭐... 거의 만능에 가깝게 여러 가지를 잘하고요. 2025년 현재는 각 AI 서비스마다 독특한 기능들을 계속 추가하고 있어요. GPT는 '딥 리서치'라는 에이전트 기능을 넣었고, Claude는 '아티팩트'나 프로젝트 관리 같은 기능을 제공하고 있죠. MCP에는 몇 가지 멋진 특징이 있어요.

  • 개방형 표준: 누구나 자유롭게 사용하고 개선할 수 있어요. 오픈소스니까요!
  • 양방향 연결: AI 모델과 데이터 소스가 서로 대화하듯 양방향으로 소통할 수 있어요.
  • 범용성: 다양한 데이터와 도구를 하나의 표준 방식으로 연결할 수 있어요.
  • 보안 및 신뢰성: 개인정보 보호하고 데이터 무결성 유지하면서 안전하게 연결해줘요.

기존 AI 서비스 기능 확장의 한계

근데 좀 아쉬운 점이 있었어요. 예를 들어 제가 Claude의 모델을 쓰면서 GPT의 딥 리서치 기능도 같이 쓰고 싶다? 그건 불가능했거든요. 각 AI 서비스가 자기네가 제공하는 기능만 쓸 수 있었기 때문이죠.

MCP의 등장으로 변화된 트렌드

그런데 Claude에서 MCP라는 걸 내놓으면서 이런 한계를 깨버렸어요! MCP 서버로 원하는 기능을 구현하면, Claude(호스트)에 MCP 서버의 클라이언트를 추가해서 새로운 기능을 마음껏 사용할 수 있게 된 거죠. 쉽게 말해 MCP는 AI 에이전트가 외부 데이터나 시스템을 효과적으로 활용할 수 있게 해주는 연결 프로토콜이에요. 앤트로픽(Anthropic)에서는 이걸 'AI 앱을 위한 USB-C 포트'라고 비유했는데, 정말 적절한 표현인 것 같아요. 다양한 데이터 소스와 도구들을 표준화된 방식으로 쉽게 연결할 수 있게 해주니까요.  

MCP, 왜 중요하고 어떻게 작동할까요?

MCP의 중요성

사실 AI가 발전하면서 외부 데이터나 시스템에 접근하는 게 정말 중요해졌어요. 예전에는 각 데이터마다 별도의 API 연결을 설정하고 관리해야 했는데, 이게 얼마나 복잡하고 귀찮았는지 몰라요! MCP는 이런 문제를 해결하기 위한 개방형 표준 프로토콜이에요. 마치 모든 장치를 연결하는 만능 어댑터 같은 거죠.

MCP는 어떻게 작동하나요?

출처: 엔트로픽 MCP 아티클(https://modelcontextprotocol.io/introduction)

MCP는 기본적으로 클라이언트-서버 구조로 작동해요. 핵심 요소는 이렇게 세 가지예요.

  1. 호스트(Host): AI 애플리케이션을 담고 조정하는 역할을 해요. Claude Desktop이나 IDE, AI 도구 같은 것들이죠.
  2. MCP 클라이언트: 호스트가 만들고, 서버와 독립적으로 연결을 유지해요.
  3. MCP 서버: 특별한 기능과 컨텍스트를 제공해주는 역할을 해요.

요즘 많이 사용되는 MCP 호스트로는 Claude, Cursor, WindSurf, Cline, Goose 등이 있어요. 이들에 MCP 클라이언트를 추가 할 수 있어요. 그리고 인기 있는 MCP 서버로는 Smithery.ai에서 제공하는 Sequential Thinking(구조화된 사고 과정), Desktop Commander(터미널 명령 실행, 파일 관리), GitHub(GitHub API 접근), Brave Search(웹 검색 기능) 등이 있답니다.

어떤 순서로 작동하나요? (워크플로우)

  1. 먼저 호스트 앱이 MCP 클라이언트를 만들고, 이 클라이언트가 MCP 서버와 연결해요.
  2. 서버는 클라이언트에게 "이런 데이터가 있어요~"라고 컨텍스트 정보를 알려주고, 클라이언트는 이 정보를 호스트에 전달해요.
  3. AI 모델이 "이 작업 좀 해주세요"라고 요청하면, 호스트는 이 요청을 알맞은 클라이언트에게 넘겨요.
  4. 클라이언트는 서버로부터 결과를 받아 호스트에 전달하고, 호스트는 이 결과를 AI 모델에게 제공해요.

기존 API와 비교하면 어떨까요? MCP는 모든 연결을 표준화된 방식으로 관리하고, 양방향 통신이 가능하며, 일반 사용자도 쉽게 활용할 수 있어요. 또 확장성이 뛰어나고, 여러 시스템 간에 컨텍스트를 유지할 수 있다는 장점이 있죠. 오픈소스 생태계를 통해 계속 발전하고 있고요!

MCP를 어디에 활용할 수 있을까요?

  • AI 에이전트와 자동화 시스템: AI 에이전트가 여러 작업을 스스로 수행할 수 있도록 도와줘요.
  • 외부 데이터 연동 챗봇: 챗봇이 실시간으로 외부 데이터를 활용해서 더 정확하고 풍부한 정보를 제공할 수 있어요.
  • LLM 기반 지식 검색 시스템: 대규모 언어 모델을 활용한 차세대 지식 검색 시스템을 만드는 데 아주 중요한 역할을 해요.

이렇게 보면 MCP가 얼마나 강력하고 유용한지 느껴지시나요? AI의 능력을 확장하는 데 정말 중요한 역할을 하고 있어요. 앞으로 AI 서비스들이 더욱 다양한 기능을 제공하고 서로 연결되는 데 MCP가 큰 역할을 할 거예요!

출처 : https://introduce-ai.tistory.com/entry/Retrieval-Augmented-GenerationRAG%EC%9D%98-%ED%9D%90%EB%A6%84%EA%B3%BC-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90

 

 

RAG란?

RAG란, LLM이 검색된 결과에 근거하여 답변하는 방식.

RAG의 뿌리는 페이스북(현 메타) AI 리서치 논문 (https://arxiv.org/pdf/2005.11401.pdf)이라고 볼수 있다.

 

  1. LLM은 환각(Hallucination)의 한계를 가짐. 또한 공개되지 않은 특정 도메인 분야 지식에 대한 작업에서 효과가 떨어짐. 또한 학습 시점 이후 데이터에 대한 정보가 없음
  2. 반면 RAG는 최신 데이터에 액세스할 수 있으므로 정보의 최신성을 갖추고, 분야별 애플리케이션에서 우수한 성능을 낼 수 있음
  3. 또한. 환각 효과를 보정/보완할 수 있음

 

[기본적인 RAG 수행 흐름] (아래 그림 참조)

  1. 문서 임베딩을 통한 벡터 DB 생성/저장
  2. 쿼리에 대한 벡터화 (이때도, 임베딩 활용)
  3. 쿼리 벡터와 관련이 높은 상위 N개의 항목을 벡터 DB에서 추출하여, LLM에 쿼리와 함께 제공
  4. LLM의 응답.
RAG의 기본적인 수행 흐름
 

[검색 방식에 대한 참고 자료]

-일반적인 Ensemble Retrieval의 흐름

검색에는 크게 3가지 방법론이 있다.

  1. 키워드 빈도 검색
  2. Dense 벡터 검색
  3. Graph 기반 검색

- 키워드의 빈도를 활용하는 대표적인 방법이, 바로 잘 알려져 있는 TF-IDF이다.

  빈도를 활용한 검색 방법론 중 SOTA는 BM25이다.

  그러나 빈도 키워드 검색은 유의어와 동의어를 포함하여 검색할수 없다는 태생적인 한계가 있다..

 

- Dense 벡터 기준으로 SOTA는 LLM 기반 임베딩 모델이다.

  학습에 의존하므로, 신규 키워드에 대해 성능이 떨어지는 단점이 있다. (그러나, 이를 대량의 데이터로 커버)

 

- Graph 자체는 오래 사용되어왔지만, 검색에 적용하는 것은 신생에 가깝다.

  어떤 Grapg DB 형태가 좋은지 그 실용성을 체크해봐야한다.

 

  현재로서는 1번과 2번을 결합하여 활용하는게 일반적이다.(상호 간의 단점을 커버).

Ensemble Retrieval의 흐름 (출처 : medium, https://cdn-images-1.medium.com/v2/resize:fit:800/1*u4wm-Jn1ZnGhSxxhBLx_CA.png)

 

- Semantic Search 시 활용되는, 임베딩 벡터 만드는 과정

임베딩 벡터 만드는 과정 (출처 : pdf로 저장해놨으나, 찾을수 없음)

 

- 또다른 RAG의 기본/Advanced 아키텍쳐 + 7가지의 Failure point

Advanced RAG 아키텍쳐 (출처 : https://cobusgreyling.medium.com/seven-rag-engineering-failure-points-02ead9cc2532)
 

 

[ RAG의 3가지 주요 패러다임 ]

RAG을 3가지의 패러다임으로 구분하고, 그 발전 방식을 확인해보자

 

< 기본 RAG >

기본 RAG는 RAG 연구의 초기 방법론을 지칭. 전통적인 인덱싱, 검색 및 생성 과정을 포함

낮은 검색 정확도, 응답 생성의 낮은 품질, 증강 과정의 어려움 등에 따라 발생하는 불필요한 반복, 부정확한 정보, 잘못된 문맥의 통합 등이 발생 가능

 

기본 RAG의 주요 구성요소로는 인덱싱(Indexing), 검색(Retrieve), 생성(Generation)

 

1. 인덱싱

  1-1. 데이터 인덱싱
  - 데이터 정제, 데이터 검색/처리를 효율적으로 하기위한 기초 처리
  - 또는 문서를 구조화하는 방법
  - 랭체인 기준: Document loaders

  1-2. 청크 분할
  - 실제 문서를 더 작은 단위(Chunk)단위로 쪼갬. 언어 모델이 처리할수 있는 맥락의 양을 고려하는 행위.
  - 랭체인 기준: Text Splitters

  1-3. 임베딩 및 인덱스 생성
  - 청크 단위의 텍스트를 벡터로 인코딩. 임베딩 모델은 정확도는 물론 높은 추론속도 요구됨.
  - 텍스트 청크와 임베딩 값을 키-값 쌍 형태로 저장
  - 랭체인 기준 : Text embedding models, Vector stores

2. 검색
 
  - 랭체인 기준 : Retriver

  2-1. 쿼리에 대한 벡터 변환
  - 랭체인 기준 : Retriever > MultiQueryRetriever, Self Query 등

  2-2. 유사성 계산 (ex. cos, 내적, mmr)
  - 랭체인 기준 : Retriever용 함수 내 파라미터로써 구현

  2-3. 필터
  - top k, Similarity score threshold 등
  - 랭체인 기준 : Retriever용 함수 내 파라미터로써 구현
  - retriever = db.as_retriever(search_type="similarity_score_threshold", search_kwargs={"score_threshold": 0.5})

3. 생성

 3-1. 주어진 질문과 관련 문서를 결합하여 새로운 프롬프트를 생성
 - 랭체인 기준(프롬프트) : prompt > PromptTemplate, ChatMessagePromptTemplate 등
 - 랭체인 기준(문서와 결합) : Chains > RetrievalQAWithSourcesChain, RetrievalQA 등

 3-2. 프롬프트(+문서)에 기반한 답변 생성
 - 랭체인 기준(문서와 결합) : Chains > RetrievalQAWithSourcesChain, RetrievalQA 등

 

 

< Advanced RAG >

고급(Advanced) RAG는 기본 RAG의 부족한 점을 개선하기 위해 개발된 패러다임.

이는 주로 검색 및 생성의 질을 향상시키기 위한 사전 및 사후 검색 방법을 포함.

고급 RAG는 크게 검색 전 절차(Pre-Retrieval Process), 검색 후 절차(Post-Retrieval Process), RAG 파이프라인 최적화(RAG Pipeline Optimization)의 3단계로 나누어볼 수 있음

====== ============ ===== ====== ============ =====

< 검색 전 절차(Pre-Retrieval Process) >

1. 데이터 인덱싱 최적화(Optimizing Data Indexing)

  1.1 데이터 세분화 강화(Enhancing Data Granularity)
    - 불필요한 정보와 특수 문자를 제거하여 검색기의 효율성을 높이고
    - 엔티티와 용어의 모호성을 제거하며
    - 중복되거나 불필요한 정보를 최소화

  1.2 인덱스 구조 최적화(Optimizing Index Structures)
    -  청크(chunk) 크기를 조정 (랭체인 기준 : RecursiveCharacterTextSplitter, ParentDocumentRetriever 등)
    - Fixed Size Chunking
    - Content-Aware chunking : 문단/문장 단위로 잘라내기
    - Recursive chunking
    - Overlapping 테크닉
    - Parent Child chunking
    - Chunk summarization : Content-Aware chunking로 문단 추출후, N자 이하로 요약하여 요약된 문장을 임베딩
    - Extract Candidate Question : 해당 문서에 해당하는 질문 N개를 추출하여, 이를 임베딩 적용
  - 인덱스 경로의 변경 (랭체인 기준 : ParentDocumentRetriever)
  - 그래프 구조의 정보를 도입 (랭체인 기준 : LangGraph)

  1.3 메타데이터 정보 추가(Adding Metadata Information)
  - 각 데이터 청크(data chunk)에 날짜, 목적 등과 같은 메타데이터를 포함
  - 검색 효율성을 개선.

  1.4 정렬 최적화(Alignment Optimization)
  - 문서 간의 차이점 또는 정렬을 위한 전략
  - 각 문서에 적합한 가상의 질문을 만들고, 그 질문을 문서와 함께 임베딩하여 보관
  - 추후 검색시, 이를 활용

  1.5 혼합 검색(Mixed Retrieval)
  - 키워드 기반 검색, 의미 검색, 벡터 검색과 같은 다양한 검색 기술을 지능적으로 결합

2. 임베딩(Embedding)

  2.1 정밀 조정 임베딩(Fine-tuning Embedding)
  - 도메인 특화, 전문 분야 용어에 맞게 임베딩 모델 학습/튜닝
  - 본 서베이 논문에서는 BGE(BAAI 2023)를 고성능의 튜닝 가능한 모델로 소개

  2.2 동적 임베딩(Dynamic Embedding)
  - 정적 임베딩을 각 단어에 대해 하나의 벡터만을 사용하는 것으로 정의
  - 동적 임베딩은 주변 단어에 따라 단어의 임베딩이 달라질 수 있게함.
  - ex. sentence transformer embedding

 

< 검색 후 절차(Post-Retrieval Process) >

  - 검색 후 절차는 고급 RAG에서 매우 중요한 단계
  - DB에서 검색된 중요한 문맥을 질의와 결합하여 LLM에 입력하는 과정
  - 검색 결과로 나온 모든 문서를 한번에 LLM에 제공하는 것은 비효율적, LLM의 context window 크기를 초과할수 있음.
  1. 순위 다시 매기기(ReRank)
    - 검색된 결과들에 대해 쿼리와의 관련성을 다시 평가하여 Rank를 다시매김.
    - cohereAI rerank, bge-rerank, LongLLMLingua 등의 여러 방식 존재.
 
@ CoherAI Rerank 설명 (https://txt.cohere.com/rerank/)
"companies can retain an existing keyword-based (also called “lexical”) or semantic search system for the first-stage retrieval and integrate the Rerank endpoint in the second stage re-ranking"

  => ReRank를 통해서 기존의 키워드 기반의 검색 결과와 벡터 기반의 시멘틱 검색 결과를 통합할수 있다.
Rerank 흐름

 

2. 프롬프트 압축(Prompt Compression)

  - 전체 콘텍스트 길이를 줄이는 데 중점(중요하지 않은 맥락은 압축, 중요한 문단은 강조)
  - Because 검색된 문서에서 발생하는 잡음(noise)이 RAG 성능에 부정적인 영향을 끼칠수 있으므로
  - Train된 압축기로 context 압축 (Recomp [Xu et al., 2023a]), 광범위한 Context를 다룸(Long Context
[Xu et al., 2023b]),  LLM의 주요 정보 인식을 향상시키기 위해 계층적 요약 트리를 설계([Chen et al., 2023a])

 

< RAG 파이프라인 최적화 (RAG Pipeline Optimization) >

  - '검색의 효율성' vs. '검색에서의 맥락적 정보의 풍부함' 사이의 균형을 달성하는 것이 목표

 
  1. 하이브리드 검색(Hybrid Search)
   - 키워드 기반 검색, 의미론적 검색, 벡터 검색과 같은 다양한 기술을 혼합

  2. 재귀적 검색 및 쿼리 엔진(Recursive Retrieval and Query Engine)
    - 작은 Chunk로 매칭하고 부모(상위 크기 문서)의 문서를 리턴(Small2Big, Parent&Child의 개념, 랭체인 기준 :  ParentDocumentRetriever)

  3. 역추적 프롬프트(StepBack-prompt)
    - LLM이 특정 사례에 치우쳐 생각하는 것이 아니라, 일반적인 개념이나 원리에 대해 추론하도록 장려하기 위한 방법
    - 대규모 언어 모델(LLM)을 사용하여 고수준의 개념과 원리를 추출하고, 이 정보를 검색 과정에 활용
 
    - 도전적인 추론 집약적 작업에서 눈에 띄는 성능 향상을 보였음([Zheng et al., 2023])
 
  4. 서브쿼리(Subqueries)
    - 다양한 질의 전략을 통해 효율적인 검색을 도모, 복잡한 쿼리를 여러 개의 쿼리로 분리하는 것이 기초 개념
    - 가장 기본적인 순차적 청크 쿼리 외 트리 쿼리, 벡터 쿼리 등
    - "Sub-queries: LLMs tend to work better when they break down complex queries. You can build this into your RAG system such that a query is decomposed into multiple questions." (https://towardsdatascience.com/10-ways-to-improve-the-performance-of-retrieval-augmented-generation-systems-5fa2cee7cd5c)

  5. HyDE(Hypothetical Document Embeddings)
    - LLM이 생성한 문장이 사용자의 질의보다 임베딩 공간에서 더 가까울 수 있다는 가정에 기반
    - LLM을 사용하여 가상의 문서(= LLM의 답변)를 생성하고, 이를 임베딩한 후,
      이 임베딩을 사용하여 실제 문서를 검색하는 방법.
    - 질의와 문서 간의 유사성이 아닌, 답변과 문서 간의 유사성에 초점

 

 

< RAG 평가 (RAG Evaluation) >

평가는 크게 2가지 종류로 나뉘는데,

하나는 검색기나 생성기 등의 각각의 모듈들에 대한 독립적인 평가(independent evaluation)
  - 검색 모듈(Retrieval Module) : 적중률(Hit Rate), MRR, NDCG, 정확도(Precision)
  - 생성 모듈(Generation Module) : 문맥의 관련성(relevance) 또는 사용자 질의와 검색된 문서들간의 연관성

입력으로부터 출력까지의 전체 과정을 평가하는 엔드-투-엔드 평가(end-to-end evaluation; 종단간 평가)가 있음.

  - 레이블이 없는 콘텐츠(Unlabeled Content)에 대한 평가 : 답변 충실도(Answer Fidelity), 답변 관련성(Answer Relevance), 무해성(Harmlessness) 등
  - 레이블이 있는 콘텐츠(Labeled Content)에 대한 평가 : 정확도(Accuracy) 및 EM(Exact Match) 등
  - 기타 : 수행하는 작업에 따라서 별도의 지표를 사용 (ex. 질문-답변 Task)

[그림. 평가 프레임워크(Evaluation Frameworks)]

 

 

참고 자료 :

https://wikidocs.net/218450

https://arxiv.org/pdf/2312.10997v1.pdf

https://python.langchain.com/docs/modules/data_connection/retrievers/

https://discuss.pytorch.kr/t/rag-2-2/3160

 

 

 

RAG (Retrieval-Augmented Generation)

기본 개념

RAG는 외부 지식 베이스에서 관련 정보를 검색하여 LLM의 응답 생성을 보강하는 기술입니다.

처리 흐름

  1. 쿼리 분석: 사용자 질문을 벡터로 변환
  2. 검색 단계: 벡터 데이터베이스에서 유사한 문서 검색
  3. 컨텍스트 구성: 검색된 문서를 프롬프트에 포함
  4. 생성 단계: LLM이 검색된 정보를 바탕으로 답변 생성

핵심 역할

  • 실시간 정보 접근으로 할루시네이션 감소
  • 도메인 특화 지식 제공
  • 지식 베이스의 동적 업데이트 가능

MCP (Model Context Protocol)

기본 개념

MCP는 Claude와 같은 AI 모델이 다양한 외부 시스템과 안전하고 표준화된 방식으로 연결할 수 있게 하는 프로토콜입니다.

처리 흐름

  1. 연결 설정: MCP 서버와 클라이언트 간 연결 수립
  2. 리소스 탐색: 사용 가능한 도구와 리소스 확인
  3. 요청 전송: 표준화된 프로토콜로 외부 시스템에 요청
  4. 응답 처리: 받은 데이터를 LLM이 이해할 수 있는 형태로 변환

핵심 역할

  • 다양한 외부 도구/서비스와의 통합 표준화
  • 보안이 강화된 외부 시스템 접근
  • 플러그인 생태계 구축 기반 제공

Agent 시스템

기본 개념

Agent는 목표를 달성하기 위해 자율적으로 계획을 세우고 도구를 사용하여 작업을 수행하는 AI 시스템입니다.

처리 흐름

  1. 목표 설정: 사용자 요청을 구체적인 목표로 분해
  2. 계획 수립: 목표 달성을 위한 단계별 계획 생성
  3. 실행: 각 단계에서 필요한 도구 호출 및 작업 수행
  4. 모니터링: 중간 결과 평가 및 계획 수정
  5. 완료: 최종 결과 검증 및 사용자에게 보고

핵심 역할

  • 복잡한 멀티스텝 작업의 자동화
  • 동적 의사결정 및 계획 수정
  • 다양한 도구의 조합적 활용

통합적 활용

상호 보완 관계

  • RAG + Agent: Agent가 필요에 따라 RAG를 활용하여 최신 정보 검색
  • MCP + Agent: Agent가 MCP를 통해 다양한 외부 시스템과 연동
  • RAG + MCP: MCP를 통해 실시간 데이터를 RAG 파이프라인에 통합

실제 구현 시나리오

예를 들어, 고객 서비스 AI 시스템의 경우:

  1. MCP를 통해 고객 데이터베이스에 연결
  2. RAG로 제품 매뉴얼과 FAQ 검색
  3. Agent가 전체 프로세스를 조율하여 맞춤형 답변 제공

 

생성 패턴 (Creational Patterns) - 5개

Singleton - "오직 하나만"

 
 
java
public class Database {
    private static Database instance;
    private Database() {} // private 생성자로 외부 생성 차단
    
    public static Database getInstance() {
        if (instance == null) {
            instance = new Database();
        }
        return instance;
    }
}

실제 사용 예: 데이터베이스 커넥션 풀, 로거, 캐시 매니저. 전역적으로 하나의 인스턴스만 필요한 경우에 사용.

Factory Method - "어떤 클래스를 만들지는 서브클래스가 결정"

 
 
java
abstract class TransportFactory {
    abstract Transport createTransport();
    
    public void deliver() {
        Transport transport = createTransport(); // 구체적인 운송수단은 서브클래스가 결정
        transport.deliver();
    }
}

class TruckFactory extends TransportFactory {
    Transport createTransport() { return new Truck(); }
}

실제 사용 예: UI 컴포넌트 생성(윈도우/맥 버튼), 데이터베이스 드라이버 선택, 로그 출력 방식 선택

Abstract Factory - "관련된 객체들의 세트를 한번에"

 
 
java
interface GUIFactory {
    Button createButton();
    TextField createTextField();
    Checkbox createCheckbox();
}

class WindowsFactory implements GUIFactory {
    Button createButton() { return new WindowsButton(); }
    TextField createTextField() { return new WindowsTextField(); }
    Checkbox createCheckbox() { return new WindowsCheckbox(); }
}

실제 사용 예: 게임에서 테마별 UI 세트, 다양한 OS용 컴포넌트 생성, 데이터베이스별 DAO 팩토리

Builder - "복잡한 객체를 단계별로 조립"

 
 
java
class Computer {
    private String CPU, RAM, storage, GPU;
    
    static class ComputerBuilder {
        private Computer computer = new Computer();
        
        ComputerBuilder setCPU(String cpu) { computer.CPU = cpu; return this; }
        ComputerBuilder setRAM(String ram) { computer.RAM = ram; return this; }
        ComputerBuilder setStorage(String storage) { computer.storage = storage; return this; }
        Computer build() { return computer; }
    }
}

Computer computer = new Computer.ComputerBuilder()
    .setCPU("Intel i7")
    .setRAM("16GB")
    .setStorage("1TB SSD")
    .build();

실제 사용 예: SQL 쿼리 빌더, 복잡한 설정 객체 생성, HTTP 요청 빌더

Prototype - "복사해서 새로 만들기"

 
 
java
abstract class Shape implements Cloneable {
    protected String type;
    
    abstract void draw();
    
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

// 사용: 기존 도형을 복제해서 새 도형 생성
Shape clonedShape = (Shape) originalShape.clone();

실제 사용 예: 게임에서 몬스터/아이템 복제, 문서 템플릿 복사, 복잡한 설정 객체 복제

 

구조 패턴 (Structural Patterns) 상세 설명

1. Adapter - "다른 규격을 맞춰주는 어댑터"

핵심 개념: 호환되지 않는 인터페이스를 가진 클래스들이 함께 작동할 수 있게 해주는 패턴

실생활 비유:

  • 한국의 220V 플러그를 미국의 110V 콘센트에 꽂을 때 사용하는 어댑터
  • USB-C를 USB-A로 변환해주는 어댑터
 
 
java
// 기존에 있던 레거시 시스템 (수정할 수 없음)
class LegacyPaymentSystem {
    void makePayment(String amount, String currency) {
        System.out.println("Legacy: 결제 " + amount + " " + currency);
    }
}

// 새로운 시스템에서 기대하는 인터페이스
interface ModernPaymentInterface {
    void pay(double amount);
}

// 어댑터: 레거시 시스템을 새로운 인터페이스에 맞춤
class PaymentAdapter implements ModernPaymentInterface {
    private LegacyPaymentSystem legacySystem;
    
    public PaymentAdapter(LegacyPaymentSystem legacySystem) {
        this.legacySystem = legacySystem;
    }
    
    @Override
    public void pay(double amount) {
        // 새로운 형식을 레거시 형식으로 변환
        legacySystem.makePayment(String.valueOf(amount), "KRW");
    }
}

// 사용
LegacyPaymentSystem legacy = new LegacyPaymentSystem();
ModernPaymentInterface payment = new PaymentAdapter(legacy);
payment.pay(1000.0); // 새로운 방식으로 호출하지만 내부적으로는 레거시 방식 사용

언제 사용하나?

  • 기존 라이브러리를 새 프로젝트에 통합할 때
  • 레거시 코드를 건드리지 않고 새 시스템과 연동할 때
  • 서드파티 API를 내 프로젝트의 인터페이스에 맞출 때

2. Bridge - "추상화와 구현을 분리"

핵심 개념: 구현부와 추상부를 분리해서 둘 다 독립적으로 변화할 수 있게 하는 패턴

실생활 비유:

  • TV 리모컨(추상)과 TV 본체(구현)의 관계
  • 리모컨은 하나지만 삼성TV, LG TV, 소니TV 등 다양한 TV를 조작 가능
 
 
java
// 구현부 인터페이스 (실제 작업을 수행)
interface DrawingAPI {
    void drawCircle(int x, int y, int radius);
    void drawRectangle(int x, int y, int width, int height);
}

// 구체적인 구현 1: Windows에서 그리기
class WindowsDrawingAPI implements DrawingAPI {
    public void drawCircle(int x, int y, int radius) {
        System.out.println("Windows API로 원 그리기: (" + x + "," + y + ") 반지름:" + radius);
    }
    
    public void drawRectangle(int x, int y, int width, int height) {
        System.out.println("Windows API로 사각형 그리기");
    }
}

// 구체적인 구현 2: Linux에서 그리기
class LinuxDrawingAPI implements DrawingAPI {
    public void drawCircle(int x, int y, int radius) {
        System.out.println("Linux X11로 원 그리기: (" + x + "," + y + ") 반지름:" + radius);
    }
    
    public void drawRectangle(int x, int y, int width, int height) {
        System.out.println("Linux X11로 사각형 그리기");
    }
}

// 추상부 (Bridge 역할)
abstract class Shape {
    protected DrawingAPI drawingAPI; // 구현부와 연결하는 다리
    
    protected Shape(DrawingAPI drawingAPI) {
        this.drawingAPI = drawingAPI;
    }
    
    abstract void draw();
}

// 구체적인 추상부
class Circle extends Shape {
    private int x, y, radius;
    
    public Circle(int x, int y, int radius, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x; this.y = y; this.radius = radius;
    }
    
    public void draw() {
        drawingAPI.drawCircle(x, y, radius); // 실제 그리기는 구현부에 위임
    }
}

// 사용
DrawingAPI windowsAPI = new WindowsDrawingAPI();
DrawingAPI linuxAPI = new LinuxDrawingAPI();

Shape circle1 = new Circle(5, 10, 3, windowsAPI); // Windows에서 그릴 원
Shape circle2 = new Circle(5, 10, 3, linuxAPI);   // Linux에서 그릴 원

circle1.draw(); // Windows API로 원 그리기
circle2.draw(); // Linux X11로 원 그리기

왜 좋은가?

  • 플랫폼이 바뀌어도 Shape 코드는 변경 불필요
  • 새로운 도형 추가 시 DrawingAPI는 건드리지 않음
  • 새로운 플랫폼 추가 시 Shape는 건드리지 않음

언제 사용하나?

  • 크로스 플랫폼 애플리케이션 개발
  • 데이터베이스 추상화 (MySQL, PostgreSQL, Oracle 등을 같은 인터페이스로)
  • 여러 구현체를 런타임에 교체해야 할 때

3. Composite - "개별 객체와 복합 객체를 동일하게 처리"

핵심 개념: 트리 구조로 객체들을 구성하여 부분-전체 계층을 표현하는 패턴

실생활 비유:

  • 파일 시스템: 파일과 폴더를 동일하게 처리 (폴더 안에 파일/폴더가 들어있음)
  • 회사 조직도: 개인과 팀을 동일하게 처리 (팀 안에 개인/하위팀이 들어있음)
 
 
java
// 공통 인터페이스
interface FileSystemItem {
    void showDetails();
    int getSize();
}

// 개별 객체 (Leaf): 파일
class File implements FileSystemItem {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void showDetails() {
        System.out.println("파일: " + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

// 복합 객체 (Composite): 폴더
class Folder implements FileSystemItem {
    private String name;
    private List<FileSystemItem> items = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileSystemItem item) {
        items.add(item);
    }
    
    public void remove(FileSystemItem item) {
        items.remove(item);
    }
    
    public void showDetails() {
        System.out.println("폴더: " + name + " (전체 크기: " + getSize() + "KB)");
        // 하위 항목들도 모두 보여주기
        for (FileSystemItem item : items) {
            System.out.print("  "); // 들여쓰기
            item.showDetails();
        }
    }
    
    public int getSize() {
        int totalSize = 0;
        for (FileSystemItem item : items) {
            totalSize += item.getSize(); // 모든 하위 항목의 크기 합계
        }
        return totalSize;
    }
}

// 사용
Folder documents = new Folder("Documents");
documents.add(new File("resume.pdf", 150));
documents.add(new File("photo.jpg", 300));

Folder projects = new Folder("Projects");
projects.add(new File("code.java", 50));
projects.add(new File("readme.md", 5));

documents.add(projects); // 폴더 안에 폴더 추가

// 개별 파일과 전체 폴더를 동일하게 처리
documents.showDetails();
/*
출력:
폴더: Documents (전체 크기: 505KB)
  파일: resume.pdf (150KB)
  파일: photo.jpg (300KB)
  폴더: Projects (전체 크기: 55KB)
    파일: code.java (50KB)
    파일: readme.md (5KB)
*/

언제 사용하나?

  • 트리 구조의 데이터를 다룰 때
  • 개별 객체와 객체 그룹을 동일하게 처리하고 싶을 때
  • GUI 컴포넌트 (버튼, 패널, 윈도우를 모두 Component로 처리)

4. Decorator - "기능을 덧붙이는 랩핑지"

핵심 개념: 객체에 동적으로 새로운 기능을 추가할 수 있는 패턴

실생활 비유:

  • 햄버거에 치즈, 베이컨, 양상추 등을 추가하는 것
  • 선물을 포장지, 리본, 카드 등으로 꾸미는 것
 
 
java
// 기본 인터페이스
interface Coffee {
    String getDescription();
    double getCost();
}

// 기본 커피 (장식될 대상)
class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }
    
    public double getCost() {
        return 2.0;
    }
}

// 데코레이터 기본 클래스
abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee; // 장식할 객체
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
    
    public String getDescription() {
        return coffee.getDescription();
    }
    
    public double getCost() {
        return coffee.getCost();
    }
}

// 구체적인 데코레이터들
class Milk extends CoffeeDecorator {
    public Milk(Coffee coffee) {
        super(coffee);
    }
    
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }
    
    public double getCost() {
        return coffee.getCost() + 0.5;
    }
}

class Sugar extends CoffeeDecorator {
    public Sugar(Coffee coffee) {
        super(coffee);
    }
    
    public String getDescription() {
        return coffee.getDescription() + ", Sugar";
    }
    
    public double getCost() {
        return coffee.getCost() + 0.3;
    }
}

class Whip extends CoffeeDecorator {
    public Whip(Coffee coffee) {
        super(coffee);
    }
    
    public String getDescription() {
        return coffee.getDescription() + ", Whip";
    }
    
    public double getCost() {
        return coffee.getCost() + 0.8;
    }
}

// 사용 - 여러 장식을 중첩해서 적용
Coffee myCoffee = new SimpleCoffee();
System.out.println(myCoffee.getDescription() + " = $" + myCoffee.getCost());
// "Simple Coffee = $2.0"

myCoffee = new Milk(myCoffee);
System.out.println(myCoffee.getDescription() + " = $" + myCoffee.getCost());
// "Simple Coffee, Milk = $2.5"

myCoffee = new Sugar(myCoffee);
System.out.println(myCoffee.getDescription() + " = $" + myCoffee.getCost());
// "Simple Coffee, Milk, Sugar = $2.8"

myCoffee = new Whip(myCoffee);
System.out.println(myCoffee.getDescription() + " = $" + myCoffee.getCost());
// "Simple Coffee, Milk, Sugar, Whip = $3.6"

상속과의 차이점:

  • 상속: 컴파일 타임에 기능이 정해짐 (MilkSugarCoffee 클래스를 미리 만들어야 함)
  • 데코레이터: 런타임에 동적으로 기능 조합 가능

언제 사용하나?

  • 객체에 기능을 동적으로 추가/제거하고 싶을 때
  • 상속으로는 너무 많은 클래스가 생길 때 (조합 폭발)
  • Java의 InputStream/OutputStream 계층

5. Facade - "복잡한 것을 간단하게 보이는 창구"

핵심 개념: 복잡한 서브시스템에 대한 간단한 인터페이스를 제공하는 패턴

실생활 비유:

  • 은행 창구 직원 (복잡한 내부 업무를 대신 처리해줌)
  • 리셉셔니스트 (호텔의 복잡한 서비스들을 간단히 요청할 수 있게 해줌)
 
 
java
// 복잡한 서브시스템들
class CPU {
    public void freeze() { System.out.println("CPU 정지"); }
    public void jump(long position) { System.out.println("CPU 점프 to " + position); }
    public void execute() { System.out.println("CPU 실행 시작"); }
}

class Memory {
    public void load(long position, byte[] data) { 
        System.out.println("메모리 로드: " + data.length + " bytes at " + position); 
    }
}

class HardDrive {
    public byte[] read(long lba, int size) { 
        System.out.println("하드디스크에서 " + size + " bytes 읽기");
        return new byte[size];
    }
}

class SoundCard {
    public void beep() { System.out.println("부팅음 재생"); }
}

// Facade 클래스 - 복잡한 부팅 과정을 간단하게 만듦
class ComputerFacade {
    private CPU cpu;
    private Memory memory;
    private HardDrive hardDrive;
    private SoundCard soundCard;
    
    public ComputerFacade() {
        this.cpu = new CPU();
        this.memory = new Memory();
        this.hardDrive = new HardDrive();
        this.soundCard = new SoundCard();
    }
    
    // 복잡한 부팅 절차를 하나의 간단한 메서드로
    public void startComputer() {
        System.out.println("컴퓨터 부팅 시작...");
        cpu.freeze();
        memory.load(0, hardDrive.read(0, 1024));
        cpu.jump(0);
        cpu.execute();
        soundCard.beep();
        System.out.println("부팅 완료!");
    }
    
    public void shutdownComputer() {
        System.out.println("컴퓨터 종료 중...");
        // 복잡한 종료 절차들...
        System.out.println("종료 완료!");
    }
}

// 사용 - 복잡한 내부 과정을 몰라도 됨
ComputerFacade computer = new ComputerFacade();
computer.startComputer();  // 간단한 호출로 복잡한 작업 수행
computer.shutdownComputer();

왜 좋은가?

  • 클라이언트는 복잡한 내부 구조를 알 필요 없음
  • 서브시스템의 변경이 클라이언트에 영향을 주지 않음
  • 자주 사용되는 복잡한 작업을 간단하게 만들 수 있음

언제 사용하나?

  • 복잡한 라이브러리나 API를 간단하게 사용하고 싶을 때
  • 레거시 시스템을 현대적인 인터페이스로 감싸고 싶을 때

6. Flyweight - "많은 객체를 효율적으로 관리"

핵심 개념: 많은 수의 객체를 효율적으로 지원하기 위해 공통 데이터를 공유하는 패턴

실생활 비유:

  • 도서관의 책들 (같은 책을 여러 사람이 빌려볼 수 있지만 책 자체는 하나)
  • 워드 프로세서의 문자들 (같은 글꼴/크기의 'A'는 하나만 만들고 위치만 다르게)
 
 
java
// 내재적 상태 (Intrinsic State) - 공유되는 부분
class TreeType {
    private String name;     // 나무 종류
    private String color;    // 색상  
    private String sprite;   // 이미지

    public TreeType(String name, String color, String sprite) {
        this.name = name;
        this.color = color;
        this.sprite = sprite;
    }

    // 외재적 상태(Extrinsic State)를 매개변수로 받아서 렌더링
    public void render(Canvas canvas, int x, int y, int size) {
        System.out.println("렌더링: " + name + "(" + color + ") at (" + x + "," + y + ") size:" + size);
        // 실제로는 canvas에 sprite를 그림
    }
}

// Flyweight Factory - 공유 객체들을 관리
class TreeTypeFactory {
    private static Map<String, TreeType> treeTypes = new HashMap<>();
    
    public static TreeType getTreeType(String name, String color, String sprite) {
        String key = name + "-" + color + "-" + sprite;
        
        TreeType treeType = treeTypes.get(key);
        if (treeType == null) {
            System.out.println("새로운 TreeType 생성: " + key);
            treeType = new TreeType(name, color, sprite);
            treeTypes.put(key, treeType);
        }
        return treeType;
    }
    
    public static int getCreatedTreeTypesCount() {
        return treeTypes.size();
    }
}

// 외재적 상태 (Extrinsic State) - 각 나무마다 다른 부분
class Tree {
    private int x, y, size;           // 위치와 크기 (각 나무마다 다름)
    private TreeType treeType;        // 공유되는 부분

    public Tree(int x, int y, int size, TreeType treeType) {
        this.x = x;
        this.y = y;
        this.size = size;
        this.treeType = treeType;
    }

    public void render(Canvas canvas) {
        treeType.render(canvas, x, y, size); // 공유 객체에 개별 데이터 전달
    }
}

// 숲 (많은 나무들을 관리)
class Forest {
    private List<Tree> trees = new ArrayList<>();

    public void plantTree(int x, int y, int size, String name, String color, String sprite) {
        TreeType treeType = TreeTypeFactory.getTreeType(name, color, sprite);
        Tree tree = new Tree(x, y, size, treeType);
        trees.add(tree);
    }

    public void render(Canvas canvas) {
        trees.forEach(tree -> tree.render(canvas));
    }
}

// 사용
Forest forest = new Forest();
Canvas canvas = new Canvas();

// 10,000그루의 나무 심기
for (int i = 0; i < 10000; i++) {
    forest.plantTree(
        random(0, 1000), random(0, 1000), random(1, 10),
        "Oak", "Green", "oak_sprite.png"  // 대부분 같은 종류
    );
}

// 실제로는 TreeType 객체가 몇 개만 생성됨
System.out.println("생성된 TreeType 개수: " + TreeTypeFactory.getCreatedTreeTypesCount());
// "생성된 TreeType 개수: 1" (10,000그루지만 TreeType은 1개만!)

forest.render(canvas);

메모리 절약 효과:

  • Flyweight 사용 안 함: 10,000개 Tree 객체 × 전체 데이터 = 매우 큰 메모리
  • Flyweight 사용: 1개 TreeType 객체 + 10,000개의 위치 데이터 = 훨씬 적은 메모리

언제 사용하나?

  • 많은 수의 비슷한 객체를 생성해야 할 때
  • 객체 생성 비용이 클 때
  • 게임의 파티클 시스템, 텍스트 에디터의 문자 렌더링

7. Proxy - "다른 객체에 대한 대리자"

핵심 개념: 다른 객체에 대한 인터페이스 역할을 하는 객체를 제공하는 패턴

실생활 비유:

  • 변호사 (본인 대신 법정에서 변론)
  • 부동산 중개사 (집주인 대신 집을 보여줌)
  • 신용카드 (실제 돈 대신 결제 처리)
 
 
java
interface Image {
    void display();
}

// 실제 객체 (비용이 많이 드는 작업)
class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadImageFromDisk(); // 무거운 작업 (실제로는 파일 로딩)
    }

    private void loadImageFromDisk() {
        System.out.println("디스크에서 이미지 로딩: " + filename);
        // 실제로는 시간이 오래 걸리는 작업
        try { Thread.sleep(2000); } catch (InterruptedException e) {}
    }

    public void display() {
        System.out.println("이미지 표시: " + filename);
    }
}

// Proxy 객체
class ProxyImage implements Image {
    private String filename;
    private RealImage realImage; // 실제 객체 (필요할 때까지 생성하지 않음)

    public ProxyImage(String filename) {
        this.filename = filename;
        // 실제 이미지는 아직 로딩하지 않음!
    }

    public void display() {
        if (realImage == null) {
            System.out.println("프록시: 실제 이미지를 처음 요청받았으므로 로딩 시작");
            realImage = new RealImage(filename); // 지연 로딩 (Lazy Loading)
        }
        realImage.display();
    }
}

// 사용
System.out.println("=== 프록시 사용 ===");
Image image1 = new ProxyImage("photo1.jpg");
Image image2 = new ProxyImage("photo2.jpg");

System.out.println("이미지 객체 생성 완료 (아직 실제 로딩은 안 됨)");

System.out.println("\n첫 번째 이미지 표시:");
image1.display(); // 이때 실제 로딩 발생

System.out.println("\n첫 번째 이미지 다시 표시:");
image1.display(); // 이미 로딩되어 있으므로 바로 표시

/*
출력:
=== 프록시 사용 ===
이미지 객체 생성 완료 (아직 실제 로딩은 안 됨)

첫 번째 이미지 표시:
프록시: 실제 이미지를 처음 요청받았으므로 로딩 시작
디스크에서 이미지 로딩: photo1.jpg
이미지 표시: photo1.jpg

첫 번째 이미지 다시 표시:
이미지 표시: photo1.jpg
*/

Proxy의 다양한 종류:

  1. 가상 프록시 (Virtual Proxy) - 위 예제처럼 지연 로딩
  2. 보안 프록시 (Protection Proxy) - 접근 권한 제어
  3. 캐싱 프록시 - 결과를 캐시해서 성능 향상
  4. 원격 프록시 (Remote Proxy) - 원격 객체를 로컬에서 사용하는 것처럼
 
 
java
// 보안 프록시 예제
class ProtectionProxyImage implements Image {
    private RealImage realImage;
    private String filename;
    private String userRole;

    public ProtectionProxyImage(String filename, String userRole) {
        this.filename = filename;
        this.userRole = userRole;
    }

    public void display() {
        if (hasAccess()) {
            if (realImage == null) {
                realImage = new RealImage(filename);
            }
            realImage.display();
        } else {
            System.out.println("접근 권한이 없습니다!");
        }
    }

    private boolean hasAccess() {
        return "ADMIN".equals(userRole) || "USER".equals(userRole);
    }
}

언제 사용하나?

  • 무거운 객체의 생성을 지연하고 싶을 때 (가상 프록시)
  • 객체 접근을 제어하고 싶을 때 (보안 프록시)
  • 네트워크를 통한 객체 접근 시 (원격 프록시)
  • 결과를 캐시하고 싶을 때 (캐싱 프록시)

구조 패턴들의 공통점과 차이점

공통점: 모두 객체들 간의 관계를 다루며, 복잡성을 줄이고 유연성을 높임

차이점:

  • Adapter: 인터페이스 변환에 집중
  • Bridge: 추상화와 구현의 분리에 집중
  • Composite: 트리 구조 처리에 집중
  • Decorator: 기능 확장에 집중
  • Facade: 복잡성 숨기기에 집중
  • Flyweight: 메모리 최적화에 집중
  • Proxy: 접근 제어나 지연 처리에 집중

 

 

행동 패턴 (Behavioral Patterns) - 11개

Chain of Responsibility - "책임의 사슬로 요청 처리"

 
 
java
abstract class Logger {
    protected int level;
    protected Logger nextLogger;
    
    void setNext(Logger nextLogger) { this.nextLogger = nextLogger; }
    
    void logMessage(int level, String message) {
        if (this.level <= level) {
            write(message);
        }
        if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
    
    abstract void write(String message);
}

// ConsoleLogger -> FileLogger -> ErrorLogger 순서로 체인 구성

실제 사용 예: 로깅 시스템, 이벤트 처리, 권한 검사, 웹 미들웨어

Command - "명령을 객체로 만들어 저장/실행"

 
 
java
interface Command {
    void execute();
    void undo();
}

class LightOnCommand implements Command {
    private Light light;
    
    void execute() { light.on(); }
    void undo() { light.off(); }
}

class RemoteControl {
    private Command[] commands = new Command[7];
    private Command lastCommand;
    
    void pressButton(int slot) {
        commands[slot].execute();
        lastCommand = commands[slot];
    }
    
    void pressUndo() { lastCommand.undo(); }
}

실제 사용 예: GUI 버튼 액션, Undo/Redo 기능, 매크로 기능, 작업 큐

Interpreter - "언어의 문법을 해석"

 
 
java
interface Expression {
    boolean interpret(String context);
}

class TerminalExpression implements Expression {
    private String data;
    
    boolean interpret(String context) {
        return context.contains(data);
    }
}

class OrExpression implements Expression {
    private Expression expr1, expr2;
    
    boolean interpret(String context) {
        return expr1.interpret(context) || expr2.interpret(context);
    }
}

// "Java OR Python" 같은 표현식 해석

실제 사용 예: 정규 표현식, SQL 파서, 수식 계산기, 설정 파일 파서

Iterator - "컬렉션의 요소들을 순차적으로 접근"

 
 
java
interface Iterator {
    boolean hasNext();
    Object next();
}

class BookShelf implements Iterable {
    private Book[] books;
    
    public Iterator iterator() {
        return new BookShelfIterator(books);
    }
}

class BookShelfIterator implements Iterator {
    private Book[] books;
    private int position = 0;
    
    boolean hasNext() { return position < books.length; }
    Object next() { return books[position++]; }
}

// 사용: for-each 루프와 같은 방식으로 순회
for (Book book : bookShelf) {
    System.out.println(book.getTitle());
}

실제 사용 예: Java의 Collection 프레임워크, 데이터베이스 결과 집합 순회

Mediator - "객체들 간의 복잡한 상호작용을 중재"

 
 
java
interface Mediator {
    void notify(Component sender, String event);
}

class DialogMediator implements Mediator {
    private Button button;
    private Textbox textbox;
    private Checkbox checkbox;
    
    void notify(Component sender, String event) {
        if (sender == checkbox && event.equals("check")) {
            textbox.setEnabled(false);
            button.setEnabled(true);
        }
        // 컴포넌트들 간의 복잡한 상호작용을 중재
    }
}

실제 사용 예: GUI 다이얼로그의 컴포넌트 상호작용, 채팅방, 항공 교통 관제

Memento - "객체의 상태를 저장하고 복원"

 
 
java
class Originator { // 상태를 가진 객체
    private String state;
    
    Memento saveStateToMemento() {
        return new Memento(state);
    }
    
    void getStateFromMemento(Memento memento) {
        state = memento.getState();
    }
}

class Memento { // 상태를 저장하는 객체
    private final String state;
    
    Memento(String state) { this.state = state; }
    String getState() { return state; }
}

class CareTaker { // 메멘토를 관리하는 객체
    private List<Memento> mementoList = new ArrayList<>();
    
    void add(Memento memento) { mementoList.add(memento); }
    Memento get(int index) { return mementoList.get(index); }
}

실제 사용 예: 텍스트 에디터의 Undo 기능, 게임 저장/로드, 체크포인트 시스템

Observer - "구독-알림 시스템"

 
 
java
interface Observer {
    void update(String message);
}

class Subject {
    private List<Observer> observers = new ArrayList<>();
    
    void attach(Observer observer) { observers.add(observer); }
    void detach(Observer observer) { observers.remove(observer); }
    
    void notifyObservers(String message) {
        observers.forEach(observer -> observer.update(message));
    }
}

class NewsAgency extends Subject {
    void setNews(String news) {
        notifyObservers(news); // 모든 구독자에게 알림
    }
}

실제 사용 예: 이벤트 시스템, 모델-뷰 아키텍처, 주식 가격 변동 알림, 소셜 미디어 알림

State - "상태에 따라 행동이 바뀌는 객체"

 
 
java
interface State {
    void doAction(Context context);
}

class StartState implements State {
    void doAction(Context context) {
        System.out.println("Player is in start state");
        context.setState(this);
    }
}

class Context {
    private State state;
    
    void setState(State state) { this.state = state; }
    void doAction() { state.doAction(this); }
}

// 자판기 예: 동전없음 -> 동전있음 -> 상품선택 -> 상품배출 상태 변화

실제 사용 예: 게임 캐릭터 상태(걷기/뛰기/공격), TCP 연결 상태, 자판기 상태

Strategy - "알고리즘을 갈아끼우기"

 
 
java
interface PaymentStrategy {
    void pay(int amount);
}

class CreditCardStrategy implements PaymentStrategy {
    void pay(int amount) { System.out.println("Paid " + amount + " using Credit Card"); }
}

class PayPalStrategy implements PaymentStrategy {
    void pay(int amount) { System.out.println("Paid " + amount + " using PayPal"); }
}

class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    
    void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }
    
    void checkout(int amount) {
        paymentStrategy.pay(amount); // 선택된 전략으로 결제
    }
}

실제 사용 예: 정렬 알고리즘 선택, 결제 방식 선택, 압축 알고리즘 선택, 로그인 방식 선택

Template Method - "큰 틀은 정해두고 세부사항만 바꾸기"

 
 
java
abstract class DataMiner {
    // 템플릿 메서드: 알고리즘의 골격을 정의
    final void mine(String path) {
        openFile(path);
        extractData();
        parseData();
        analyzeData();
        sendReport();
        closeFile();
    }
    
    abstract void extractData();  // 서브클래스에서 구현
    abstract void parseData();    // 서브클래스에서 구현
    
    // 공통 구현
    void openFile(String path) { /* 파일 열기 */ }
    void closeFile() { /* 파일 닫기 */ }
}

class PDFDataMiner extends DataMiner {
    void extractData() { /* PDF 데이터 추출 */ }
    void parseData() { /* PDF 데이터 파싱 */ }
}

실제 사용 예: 데이터 처리 파이프라인, 테스트 프레임워크, 웹 프레임워크의 요청 처리

Visitor - "객체 구조를 변경하지 않고 새로운 연산 추가"

 
 
java
interface Visitor {
    void visit(Circle circle);
    void visit(Rectangle rectangle);
}

class AreaCalculator implements Visitor {
    void visit(Circle circle) { 
        area = Math.PI * circle.radius * circle.radius; 
    }
    void visit(Rectangle rectangle) { 
        area = rectangle.width * rectangle.height; 
    }
}

abstract class Shape {
    abstract void accept(Visitor visitor);
}

class Circle extends Shape {
    void accept(Visitor visitor) {
        visitor.visit(this); // 자신을 방문자에게 전달
    }
}

// 새로운 기능(둘레 계산)을 추가할 때 기존 도형 클래스 수정 없이 새 Visitor만 추가

실제 사용 예: 컴파일러의 AST 처리, 파일 시스템 탐색, 문서 구조 분석

실무에서의 활용도

매우 자주 사용: Singleton, Factory Method, Observer, Strategy, Decorator, Adapter 프레임워크에서 흔함: Template Method, Command, Iterator, Proxy 특수한 상황에서 유용: Flyweight, Interpreter, Visitor, Memento 복잡한 시스템에서: Abstract Factory, Builder, Mediator, Chain of Responsibility

+ Recent posts