DAY47

https://velog.io/@dos123789/Day47

1. 배운 내용 (Today I Learned)

1) 스프링(Spring)이란?
스프링은 "엔터프라이즈용 Java 애플리케이션 개발을 편하게 할 수 있게 해주는 오픈소스 경량급 애플리케이션 프레임워크"로 정의된다.

프레임워크(Framework): 자동차의 차체(Frame)와 같다. 바퀴, 엔진 등 모든 것을 처음부터 만드는 것이 아니라, 제공된 뼈대 위에서 내가 필요한 비즈니스 로직(서비스)을 구현하는 데 집중할 수 있게 해준다.

경량급(Lightweight): 과거의 기술(EJB)이 지나치게 복잡하고 무거웠던 것에 반해, 스프링은 불필요한 복잡성을 덜어내고 코드 작성을 단순화했다.

오픈소스: 무료이며, 거대 IT 기업(SpringSource -> VMWare 등)이 관리하여 안정적인 개발과 개선이 보장된다.

2) 스프링의 핵심 철학: POJO
스프링의 가장 큰 특징은 POJO(Plain Old Java Object) 프로그래밍을 지향한다는 점이다.

외부 라이브러리나 특정 기술에 종속되지 않는 '순수한 Java 객체'를 사용하는 것이다.

이를 통해 코드가 유연해지고, 테스트와 디버깅이 쉬워진다.

이 POJO를 가능하게 하기 위해 스프링은 3대 핵심 기술(Triangle)을 지원한다.

① IoC / DI (제어의 역전 / 의존성 주입)
개념: 객체의 생성과 의존 관계 설정을 개발자가 직접 new 키워드로 하는 것이 아니라, 스프링 컨테이너(Spring)가 대신 해주는 것이다.

장점: A 객체가 B 객체를 직접 생성하면 강한 결합이 생기는데, 스프링이 중간에서 주입(Injection)해주면 결합도가 낮아진다. 덕분에 B를 C로 바꿔야 할 때 코드를 대대적으로 수정할 필요 없이 설정만 바꾸면 된다.

② AOP (관심 지향 프로그래밍)
개념: 애플리케이션의 로직을 핵심 관심 사항(비즈니스 로직)과 공통 관심 사항(로깅, 보안 등)으로 분리하는 것이다.

장점: 코드 중복을 줄일 수 있다. 예를 들어 모든 메소드에 로그를 찍어야 한다면, 일일이 코드를 넣는 게 아니라 AOP로 공통 로직을 분리해 적용하면 된다.

③ PSA (일관된 서비스 추상화)
개념: 특정 기술에 종속되지 않도록 추상화된 인터페이스를 제공하는 것이다.

예시: JDBC가 대표적이다. MySQL을 쓰다가 MariaDB로 바꾸더라도, 스프링이 제공하는 데이터베이스 추상화 계층 덕분에 기존 코드를 거의 수정하지 않고 DB 교체가 가능하다.

3) 스프링 부트(Spring Boot)란?
"스프링은 좋은데 설정이 너무 어렵다"는 단점을 해결하기 위해 등장했다.

복잡한 설정 자동화: 수많은 XML 설정이나 복잡한 의존성 관리를 자동화하여 개발자가 비즈니스 로직에만 집중하게 해준다.

내장 웹 서버: Tomcat 같은 웹 서버를 내장하고 있어, 별도의 서버 설치 없이 Jar 파일만으로도 실행이 가능하다. (배포가 엄청 쉬워짐!)

2. 부족한 점과 보완 계획

부족한 점 (Problems)
개념의 추상성: IoC와 DI가 "객체를 대신 생성해준다"는 이론은 이해했지만, 실제로 코드로 구현할 때 @Component나 @Autowired 같은 어노테이션이 내부적으로 어떻게 동작하는지 아직 와닿지 않는다.

POJO의 진정한 의미: "순수 자바 객체"가 좋다는 건 알겠는데, 프레임워크를 쓰면서 프레임워크에 종속되지 않는다는 말이 역설적으로 들리기도 한다. 이 부분에 대한 깊은 이해가 필요하다.

AOP의 적용: 공통 로직을 분리한다는 개념은 좋으나, 실제 프로젝트에서 어떤 상황에 AOP를 적용해야 효율적인지 감이 잘 오지 않는다.

보완 계획 (Action Plan)
실습으로 이해하기 (DI):

new 키워드를 사용한 코드와 스프링 DI를 사용한 코드를 직접 작성해서 비교해보는 예제를 만들어 봐야겠다. 의존성이 변경될 때 유지보수가 얼마나 쉬워지는지 몸소 체험하는 과정이 필요하다.

공식 문서 및 레퍼런스 참고:

Spring Initializr로 간단한 프로젝트를 생성해보고, build.gradle 등의 설정 파일이 어떻게 구성되는지 뜯어볼 예정이다.

PSA 심화 학습:

JDBC 외에 또 어떤 PSA 사례가 있는지(예: Transaction Manager) 찾아보고 정리해야겠다.


DAY48

https://velog.io/@dos123789/Day48

1. 배운 내용

1) 프로젝트 생성 및 환경 설정
Spring Initializr를 통해 Gradle 기반의 프로젝트를 생성합니다. Spring AI 관련 의존성을 추가하면 build.gradle에 자동으로 설정이 포함됩니다. 프로젝트 로드 시 가장 먼저 확인해야 할 파일은 두 가지입니다.

build.gradle: Spring AI 버전과 사용할 모델(Google Gemini, OpenAI 등)의 스타터 의존성 확인

application.yml (또는 properties): API Key, 모델 선택, 포트 설정 등 핵심 설정 정보

주요 build.gradle 설정 예시

Groovy

plugins {
id 'java'
id 'org.springframework.boot' version '3.5.9'
id 'io.spring.dependency-management' version '1.1.7'
}

ext {
set('springAiVersion', "1.1.2")
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// Google Gemini 연동
implementation 'org.springframework.ai:spring-ai-starter-model-google-genai'
// H2 Database
runtimeOnly 'com.h2database:h2'
}

dependencyManagement {
imports {
mavenBom "org.springframework.ai:spring-ai-bom:${springAiVersion}"
}
}
2) LLM 모델 연동 방식 (Cloud vs Local)
AI 모델을 연동하는 방식은 크게 두 가지로 나뉩니다.

외부 API 활용 (Cloud): OpenAI, Google Gemini, Claude 등 상용 모델의 API Key를 발급받아 설정 파일에 등록하여 사용합니다. 성능이 우수하지만 비용이 발생할 수 있으므로 Key 관리가 중요합니다.

로컬 LLM 활용 (Ollama): Ollama를 설치하여 내 컴퓨터(로컬)에서 모델을 직접 실행합니다. API 비용이 들지 않아 실험용으로 적합하지만, 모델 파일 용량(수 GB) 관리와 로컬 하드웨어 성능의 영향을 받습니다.

3) Controller 및 웹 요청 처리
@RestController를 사용하여 사용자의 요청을 받아 AI 모델에 전달하고 응답을 반환하는 구조를 구축합니다.

요청 처리: @RequestParam 등을 통해 URL 파라미터로 프롬프트를 전달받습니다. (예: /ai/generate?message=질문내용)

응답 형식: 단순 텍스트뿐만 아니라 JSON 형식의 응답이나, 모델이 지원하는 경우 이미지 생성 결과도 반환할 수 있습니다.

확장성: 현재는 브라우저 주소창으로 테스트하지만, 실제 서비스에서는 React 등 프론트엔드와 연동하여 JSON 데이터를 주고받는 구조로 확장됩니다.

4) 데이터베이스 연동 (H2)
AI 서비스와 데이터를 연계하기 위해 H2(In-memory DB)를 활용합니다.

애플리케이션 실행 시 테이블 생성 및 초기 데이터 삽입이 자동화되어 빠른 테스트에 유리합니다.

향후 MySQL, PostgreSQL 등 영속적인 데이터베이스로 교체하여, DB에 저장된 데이터를 기반으로 AI가 답변하는 RAG(검색 증강 생성) 방식 등으로 발전시킬 수 있습니다.

2. 부족한 점과 보완 계획

API Key 보안: 실습 중 API Key를 설정 파일에 직접 입력했으나, 실제 배포 환경에서는 환경 변수나 별도의 보안 저장소를 통해 관리하는 방법이 필요합니다.

프롬프트 엔지니어링: 단순히 질문을 던지는 것을 넘어, 원하는 형식(JSON 등)으로 정확한 답변을 얻어내기 위한 프롬프트 설계 능력이 부족함을 느꼈습니다.

로컬 리소스 관리: Ollama 사용 시 모델 다운로드로 인한 디스크 용량 부족 문제나 실행 속도 저하를 겪을 수 있어 이에 대한 최적화가 필요합니다.

환경 변수 분리: application.yml의 민감한 정보를 OS 환경 변수나 별도의 프로파일로 분리하여 관리하는 방법을 적용해 볼 예정입니다.

프론트엔드 연동 실습: 브라우저 주소창 테스트를 넘어, 간단한 화면을 구성하여 비동기(AJAX/Fetch) 통신으로 AI 응답을 받아 화면에 렌더링하는 과정을 구현해 보겠습니다.

서비스 관점 접근: 단순히 모델을 호출하는 기술을 넘어, "어떤 데이터를 기반으로 어떤 가치를 제공할 것인가"에 집중하여 미니 프로젝트를 기획해 볼 계획입니다.

마무리 Spring AI는 새로운 LLM을 만드는 도구가 아니라, 이미 존재하는 강력한 모델들을 우리 서비스에 쉽고 빠르게 접목시켜주는 도구입니다. 복잡한 연동 과정을 스프링 프레임워크가 추상화해 준 덕분에, 개발자는 비즈니스 로직과 데이터 설계에 더 집중할 수 있다는 점이 핵심입니다.


DAY49

https://velog.io/@dos123789/Day49

1. 배운 내용

1) Docker를 활용한 Vector DB(pgvector) 구축
AI 벡터 검색을 지원하기 위해 PostgreSQL에 pgvector 확장이 포함된 이미지를 Docker로 실행했다.

Docker 실행 명령어

docker run -p 5432:5432 -d \
-e POSTGRES_USER=edu \
-e POSTGRES_PASSWORD=1234 \
-e POSTGRES_DB=edudb \
-v vpg:/var/lib/postgresql/data \
--name edupgvector \
ankane/pgvector

명령어 상세 분석

docker run: 컨테이너를 생성하고 실행한다.

-p 5432:5432: 포트 포워딩 설정이다. (내 컴퓨터의 5432 포트 ↔ 컨테이너의 5432 포트). 이를 통해 Spring Boot, DBeaver, pgAdmin 등에서 localhost:5432로 접속할 수 있다.

-d: Detached 모드. 백그라운드에서 실행하여 터미널이 블로킹되지 않게 한다.

-e (환경변수): PostgreSQL 초기 계정 및 DB 정보를 설정한다. (USER, PASSWORD, DB 이름)

-v vpg:/var/lib/postgresql/data: 도커 볼륨 설정이다. 컨테이너를 삭제해도 DB 데이터가 로컬에 남아있도록 하여 실습 데이터 유실을 방지한다.

--name edupgvector: 컨테이너에 이름을 부여한다. 추후 docker stop이나 start 명령어로 관리하기 편해진다.

ankane/pgvector: 실행할 이미지 이름이다. (PostgreSQL에 pgvector 플러그인이 미리 설치된 버전)

벡터 연산 쿼리 (pgvector) DB 구축 후 DBeaver에서 다음과 같은 연산자를 통해 벡터 유사도를 계산할 수 있다.

<=>: 코사인 거리(Cosine Distance) 계산. 값이 작을수록 유사하다.

<#>: 내적(Inner Product) 계산.

SQL

-- 코사인 거리 계산 예시
SELECT tpt.*, tpt.embedding <=> '[0.2, 0.3, 0.4]' AS cosine_distance
FROM tb_pg_test tpt;
2) Spring Boot 실행 및 포트 관리
터미널에서 Gradle 래퍼를 통해 프로젝트를 실행하는 방법을 익혔다.

권한 부여: chmod +x gradlew

실행: ./gradlew bootRun

포트 충돌 해결: 만약 8080 포트가 이미 사용 중이라면 lsof -i :8080 명령어로 PID를 찾아 프로세스를 종료해야 한다.

3) Trouble Shooting: CORS (Cross-Origin Resource Sharing)
React(프론트엔드)와 Spring(백엔드)을 연동할 때 가장 빈번하게 발생하는 CORS 에러를 해결했다.

문제 상황

프론트엔드: http://localhost:5173

백엔드: http://localhost:8080

브라우저 입장에서는 두 주소의 출처(Origin)가 다르기 때문에 보안 정책상 요청을 기본적으로 차단한다.

CORS의 동작 원리

브라우저는 다른 출처로 요청을 보낼 때 보안이 최우선이다.

서버가 명시적으로 허용하지 않으면 요청을 보내지 않거나 응답을 거부한다.

특히 WebFlux나 fetch 사용 시, 실제 요청(GET/POST)을 보내기 전에 Preflight(OPTIONS) 요청을 먼저 보낸다. ("이 요청 보내도 되니?"라고 묻는 과정)

이때 서버가 Access-Control-Allow-Origin 헤더를 내려주지 않으면 본 요청은 전송되지 않는다.

해결 방법: WebConfig 설정 메인 애플리케이션 클래스가 아닌 별도의 WebConfig 클래스를 작성하여 설정을 추가해야 한다. Springai13Application 같은 메인 클래스는 서버 구동용이므로 웹 계층 설정이 적용되지 않을 수 있기 때문이다.

Java

// WebConfig는 브라우저에게 "이 서버는 저 출처(5173)의 요청을 신뢰한다"라고 알려주는 역할
@Configuration
public class WebConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:5173") // 프론트엔드 출처 허용
.allowedMethods("GET", "POST", "OPTIONS");
}
}

2. 부족한 점과 보완 계획

Docker 명령어 숙련도: 긴 docker run 명령어를 복사해서 사용했지만, 각 옵션을 안 보고 작성하라고 하면 헷갈릴 것 같다. 특히 볼륨(-v) 설정 경로에 대한 이해가 더 필요하다.

CORS의 깊은 이해: 해결 방법은 알았으나, Preflight 요청이 구체적으로 어떤 헤더를 주고받는지 네트워크 탭을 통해 직접 확인해보지 못했다.

벡터 연산자: 코사인 거리와 내적의 수학적 차이와, 어떤 상황에서 무엇을 써야 유리한지 명확하게 와닿지 않았다.

Docker Compose 도입: 매번 긴 명령어를 치는 대신 docker-compose.yml 파일을 작성하여 컨테이너를 관리하는 방법을 학습하고 적용해 볼 것이다.

네트워크 패킷 분석: 크롬 개발자 도구의 Network 탭을 열어 OPTIONS 요청(Preflight)과 그에 대한 서버의 응답 헤더를 직접 눈으로 확인해 볼 예정이다.

유사도 측정 학습: 코사인 유사도와 유클리드 거리 등 벡터 검색 알고리즘의 차이를 정리하고, 실제 검색 결과가 어떻게 달라지는지 테스트해 보겠다.

마무리 오늘 실습을 통해 로컬 환경에서 AI 서비스를 개발하기 위한 기초 인프라(DB)와 통신 구조(CORS)를 잡았다. 특히 CORS는 "오류"라기보다 브라우저의 "보안 기능"이라는 점을 명확히 인지하게 되었다.


DAY50

https://velog.io/@dos123789/Day50-ReactFastapiSpring-%EC%97%B0%EB%8F%99

.

1. 배운 내용

1) 전체 아키텍처 설계: 책임의 분리
이번 실습의 핵심은 "누가 무엇을 담당하는가"를 명확히 하는 것이다. 모든 기능을 Spring에 몰아넣지 않고, Python 생태계가 강점인 AI 기능은 FastAPI로 분리했다.

시스템 구조 및 데이터 흐름

React (Frontend): 사용자 인터페이스. 모든 요청은 Spring Boot로 보낸다.

Spring Boot (Main Backend): 게시글 CRUD, DB 저장, 인증 등을 담당하는 데이터의 주인이다. AI 요약이 필요할 때만 FastAPI에 요청을 보낸다.

FastAPI (AI Worker): Spring의 요청을 받아 OpenAI API를 호출하고 결과를 반환하는 단순한 연산/중계 서버 역할이다.

PostgreSQL: 데이터가 영구 저장되는 저장소.

왜 React가 직접 FastAPI를 호출하지 않는가? React가 FastAPI를 직접 호출하면 게시글 데이터(DB)와 AI 요약 데이터가 분리되어 관리하기 어려워진다. "게시글 저장 후 요약"이라는 하나의 트랜잭션 흐름을 제어하기 위해 Spring이 컨트롤 타워 역할을 수행해야 한다.

2) Spring Boot: WebClient와 CORS
Spring이 다른 서버(FastAPI)와 통신하기 위해 WebClient를 사용했고, 프론트엔드(React)와의 통신을 위해 CORS 설정을 적용했다.

WebClient: RestTemplate의 최신 대체 기술로, 외부 API(FastAPI)를 비동기적으로 호출할 때 사용한다. 이번 실습에서는 .block()을 사용하여 결과를 동기적으로 받아 처리했다.

CORS (Cross-Origin Resource Sharing): React(5173 포트)와 Spring(8080 포트)은 출처가 다르므로, WebConfig에서 명시적으로 allowedOrigins를 설정하여 브라우저의 보안 차단을 해결했다.

3) Docker Compose를 이용한 DB 구축
로컬 환경에 DB를 직접 설치하는 대신, docker-compose.yml을 작성하여 PostgreSQL 컨테이너를 구동했다.

Volume 설정: -v pgdata:/var/lib/postgresql/data 옵션을 통해 컨테이너가 삭제되어도 데이터가 유지되도록 설정했다.

실행 순서: Docker 컨테이너가 완전히 뜬 후 Spring Boot를 실행해야 DB 연결 오류(Connection refused)를 막을 수 있다.

4) 트러블 슈팅: 실행 순서의 중요성
개발 환경 세팅 시 순서가 매우 중요하다는 것을 배웠다.

Docker Desktop 실행

docker compose up -d (DB 실행)

docker ps로 상태 확인

Spring Boot 실행 (DB 연결 시도)

FastAPI 실행

React 실행

이 순서를 지키지 않으면 Spring이 시작되자마자 DB를 찾지 못해 종료되는 문제가 발생한다.

2. 부족한 점과 보완 계획

동기 처리의 한계: Spring의 FastApiClient에서 .block()을 사용했다. 이는 FastAPI의 응답이 올 때까지 Spring의 스레드가 대기(Blocking)한다는 뜻이다. 사용자가 많아지면 서버 전체가 느려질 수 있는 구조다.

예외 처리 미비: 만약 FastAPI 서버가 꺼져있거나 OpenAI API가 에러를 뱉을 경우, Spring에서 어떻게 처리할지에 대한 로직이 부족하다. 현재는 500 에러가 발생할 것이다.

보안: FastAPI가 포트 8000번으로 열려있어 누구나 직접 요청을 보낼 수 있다. Spring만 요청할 수 있도록 제한하는 보안 장치가 없다.

비동기 전환: WebClient의 장점을 살려 .block()을 제거하고, Mono나 Flux를 반환하여 Non-blocking 방식으로 개선해 볼 예정이다.

Circuit Breaker 도입: FastAPI에 장애가 발생했을 때 Spring이 계속 기다리지 않고 즉시 대체 로직(예: "요약 서비스 준비 중")을 수행하도록 회로 차단기 패턴을 공부해 봐야겠다.

내부 통신 보안: FastAPI가 외부 인터넷이 아닌 Docker 내부 네트워크에서만 통신하도록 설정하거나, Spring과 FastAPI 사이에 API Key 인증을 추가하여 보안을 강화할 계획이다.


DAY51

https://velog.io/@dos123789/Day51

1. 배운 내용

1) 계층형 아키텍처의 흐름 완벽 정리
Spring Boot는 역할에 따라 철저하게 계층이 나뉘어 있다는 것을 다시 한번 확인했다.

Controller: "요청을 받고 응답을 준다." (식당 웨이터)

Service: "요청받은 일을 실제로 처리한다." (주방장)

Repository: "재료(데이터)를 가져온다." (창고 관리자)

DTO/Entity: "데이터를 담는 그릇." (접시/재료)

2) 어노테이션의 마법 (IoC)
개발자가 직접 new를 해서 객체를 생성하는 것이 아니라, @Controller, @Service 같은 어노테이션만 붙여주면 스프링 컨테이너가 알아서 객체를 생성하고 관리하고 주입해준다는 점(IoC/DI)이 생산성을 얼마나 높여주는지 깨달았다.

2. 부족한 점과 보완 계획

JPA의 깊은 이해 부족: JpaRepository가 편해서 쓰고는 있지만, 내부적으로 영속성 컨텍스트(Persistence Context)가 언제 열리고 닫히는지, Lazy Loading이 구체적으로 어떤 시점에 발생하는지 설명하라면 자신이 없다.

DTO와 Entity의 분리: 실습 때는 편의상 Entity를 Controller에서 바로 반환하기도 했다. 하지만 실무에서는 Entity가 변경되면 API 스펙이 바뀌는 문제가 있어 DTO(Data Transfer Object)로 반드시 변환해야 한다는 점을 놓치고 있었다.

@Transactional의 동작 원리: 어노테이션만 붙이면 트랜잭션이 된다는 건 알지만, AOP(Aspect Oriented Programming)가 어떻게 프록시 객체를 만들어서 트랜잭션을 처리하는지 원리는 잘 모른다.

DTO 도입 리팩토링: 기존 코드에서 Controller가 Entity를 직접 반환하는 부분을 PostResponseDto 같은 클래스를 만들어 변환하여 반환하도록 수정해볼 예정이다.

영속성 컨텍스트 학습: JPA의 1차 캐시, Dirty Checking(변경 감지) 개념을 따로 정리해서 블로그에 포스팅해야겠다.

디버깅 연습: @Transactional이 붙은 메서드에 중단점(Breakpoint)을 걸고 스택 트레이스를 확인하며, 실제 프록시 객체가 어떻게 개입하는지 눈으로 확인해봐야겠다.


본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 3기(B-log) 리뷰로 작성 되었습니다.

profile
Change Up

0개의 댓글