Home

0

Spring JPA와 Flyway를 이용한 DB 마이그레이션 관리

Flyway란?Flyway는 데이터베이스 스키마 버전 관리 도구입니다. 애플리케이션 코드와 마찬가지로 DB 스키마 변경 이력(마이그레이션)을 버전으로 관리할 수 있게 해줍니다. Spring Boot + JPA 환경에서 Flyway를 사용하면 다음과 같은 이점을 얻을 수 있습니다. 팀 협업 시 DB 스키마 변경 이력을 코드로 공유 배포 환경(dev/staging/prod)마다 스키마 일관성 보장 롤백 전략 수립 가능 spring.jpa.hibernate.ddl-auto=create 대신 안전한 마이그레이션 적용 의존성 추가Gradledependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.flywaydb:flyway-core' // MySQL 사용 시 추가 (Flyway 10+부터 DB별 모듈 분리) implementation 'org.flywaydb:flyway-mysql' runtimeOnly 'com.mysql:mysql-connector-j'} Maven<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId></dependency><dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-mysql</artifactId></dependency>

0

Android WebView 브릿지 통신 완벽 정리

개요Android 하이브리드 앱은 네이티브 레이어(Kotlin/Java)와 웹 레이어(JavaScript)가 공존합니다. 이 두 레이어는 서로 다른 실행 환경에서 동작하기 때문에, 직접 함수를 호출하거나 변수를 공유할 수 없습니다. 브릿지(Bridge) 는 이 둘을 연결하는 통신 채널입니다. 예를 들어 웹에서 “카메라를 열어줘”라고 요청하면 네이티브가 처리하고 결과를 돌려주는 방식입니다. 브라우저의 fetch처럼 비동기 Promise 형태로 쓸 수 있게 추상화하는 것이 목표입니다. 통신 흐름Android WebView 브릿지는 두 방향으로 통신합니다. ┌─────────────────────────────────────────────────────────┐│ Android App ││ ││ ┌─────────────┐ ┌──────────────────────┐ ││ │ WebView │ │ Native (Kotlin) │ ││ │ │ ──────────▶ │ │ ││ │ JavaScript │ postMessage │ @JavascriptInterface│ ││ │ │ │ │ ││ │ │ ◀────────── │ evaluateJavascript │ ││ └─────────────┘ └──────────────────────┘ │└─────────────────────────────────────────────────────────┘웹 → 네이티브: window.AndroidBridge.postMessage(json)네이티브 → 웹: webView.evaluateJavascript("window.__cb(json)", null) 웹 → 네이티브: Android가 addJavascriptInterface()로 주입한 객체를 JS에서 직접 호출합니다. 네이티브 → 웹: 네이티브가 evaluateJavascript()로 JS 코드를 문자열로 실행합니다. 두 방향 모두 JSON 문자열을 메시지 포맷으로 사용하며, 복잡한 객체는 직렬화/역직렬화가 필요합니다. 1. 기본 구조

0

JavaScript - postMessage 완벽 정리

postMessage란?window.postMessage()는 서로 다른 origin을 가진 window 객체 사이에서 안전하게 메시지를 주고받을 수 있는 Web API입니다. 브라우저의 동일 출처 정책(Same-Origin Policy)은 iframe, 팝업, 다른 탭의 JavaScript가 서로 직접 접근하는 것을 막습니다. postMessage는 이 정책을 우회하지 않고, 명시적으로 허용된 출처 간에만 안전하게 통신하는 채널을 제공합니다. postMessage를 사용하는 주요 상황 상황 송신 측 수신 측 iframe ↔ 부모 페이지 iframe.contentWindow / window.parent window 팝업 ↔ 오프너 페이지 window.opener window 다른 탭 ↔ 현재 탭 BroadcastChannel 또는 window.open 반환값 window 메인 스레드 ↔ Web Worker worker 인스턴스 self (Worker 내부) 메인 스레드 ↔ Service Worker navigator.serviceWorker self (SW 내부) 1. 기본 사용법메시지 보내기targetWindow.postMessage(message, targetOrigin, [transfer]); 파라미터 타입 설명 message any 전송할 데이터. 구조화 복제 알고리즘으로 직렬화됨 targetOrigin string 수신 대상의 origin. '*'이면 모든 origin 허용 (비권장) transfer Transferable[] (선택) 소유권을 이전할 객체 목록 (ArrayBuffer 등)

0

[쿠버네티스 DevOps 구축] - Ingress NGINX 대신 Traefik 적용하기

목차 [쿠버네티스 DevOps 구축] - Ingress NGINX 대신 Traefik 적용하기 [쿠버네티스 DevOps 구축] - OpenSearch 설치하기 [쿠버네티스 DevOps 구축] - Prometheus 와 Grafana 설치하기 [쿠버네티스 DevOps 구축] - Keycloak 설치하기 [쿠버네티스 DevOps 구축] - Ingress Nginx Controller 설치하기 [쿠버네티스 DevOps 구축] - Local PC 쿠버네티스 동적 프로비저닝을 위한 StorageClass 설치 [쿠버네티스 DevOps 구축] - Local PC 에 쿠버네티스 설치하기 Ingress Nginx 의 서비스 종료2025년 11월 11일자 기준으로 쿠버네티스 공식문서에 Ingress Nginx 에 대한 서비스 지원 종료 관련 공지가 올라왔습니다. 때문에, Ingress Nginx 를 다른 서비스로 교체를 이번 전환의 목적은 단순히 Ingress Controller를 교체하는 것보다, 라우팅 설정을 조금 더 선언적으로 관리하고 HTTPS 리다이렉션, Middleware, 대시보드, CRD 기반 라우팅을 한 곳에서 정리하는 데 있었다. Ingress NGINX도 충분히 많이 쓰이는 안정적인 선택지지만, 실제 운영에서 다음과 같은 요구가 생기면 Traefik이 더 편하게 느껴질 수 있다. Ingress annotation보다 더 명확한 라우팅 구성이 필요할 때 경로 재작성, 인증, 리다이렉트 같은 기능을 재사용 가능한 단위로 관리하고 싶을 때 표준 Ingress와 전용 CRD 방식을 함께 쓰고 싶을 때 Kubernetes 외에도 Docker, Swarm, Nomad 같은 여러 provider와 비슷한 방식으로 운영하고 싶을 때 Traefik 이란

0

JavaScript - 딥링크(Deep Link), 앱링크(App Link), 웹링크(Web Link) 완벽 정리

링크 종류 개요모바일 환경에서는 단순한 URL 이상의 링크 체계가 필요합니다.앱 설치 여부, 플랫폼(iOS/Android), 특정 화면 진입 등을 고려한 다양한 링크 방식이 존재합니다. 종류 설명 대상 웹링크 (Web Link) 일반 HTTP/HTTPS URL 브라우저 딥링크 (Deep Link) 앱 특정 화면으로 직접 이동 앱 앱링크 (App Link) Android의 검증된 딥링크 (HTTPS 기반) Android 앱 유니버설 링크 (Universal Link) iOS의 검증된 딥링크 (HTTPS 기반) iOS 앱 디퍼드 딥링크 (Deferred Deep Link) 앱 미설치 시 설치 후 특정 화면 진입 앱 + 스토어 웹링크 (Web Link)가장 기본적인 형태의 링크입니다. http:// 또는 https:// 로 시작하며 브라우저에서 열립니다. https://example.com/products/123 앱이 설치되어 있어도 브라우저에서 열린다. 앱으로 연결하려면 별도의 딥링크 처리가 필요하다. SEO에 유리하고, 공유가 쉽다. 딥링크 (Deep Link)

0

JavaScript - iframe 완벽 정리

iframe 이란?<iframe> (Inline Frame) 은 현재 HTML 문서 안에 다른 HTML 문서를 삽입할 수 있는 HTML 요소입니다. 즉, 하나의 웹 페이지 안에 독립된 브라우징 컨텍스트(browsing context) 를 가진 또 다른 웹 페이지를 내포시킬 수 있습니다. <iframe src="https://example.com" width="800" height="600"></iframe> 주요 속성 속성 설명 src 삽입할 문서의 URL width / height iframe의 너비/높이 (px 또는 %) name iframe의 이름 (링크 타겟으로 활용 가능) allow 권한 정책 설정 (카메라, 마이크 등) sandbox 보안 제한 적용 loading 지연 로딩 (lazy / eager) allowfullscreen 전체화면 허용 여부 frameborder 테두리 표시 여부 (deprecated, CSS 권장) 기본 사용 예제외부 페이지 삽입

0

JavaScript - 웹앱 브릿지 패턴 완벽 정리

브릿지(Bridge)란?웹앱에서 브릿지는 서로 다른 실행 컨텍스트 간의 통신을 추상화한 레이어입니다. postMessage나 네이티브 API는 저수준(low-level)이라 메시지 타입 관리, 요청-응답 매핑, 에러 처리를 직접 구현해야 합니다.브릿지 패턴을 적용하면 이를 추상화하여 RPC(Remote Procedure Call)처럼 쉽게 사용할 수 있습니다. 브릿지가 필요한 대표적인 상황 상황 통신 대상 iframe 내부 ↔ 부모 페이지 다른 origin의 window 웹뷰(WebView) ↔ 네이티브 앱 React Native, iOS, Android Web Worker ↔ 메인 스레드 백그라운드 스레드 브라우저 확장(Extension) ↔ 웹 페이지 확장 콘텐츠 스크립트 1. 기본 브릿지 패턴 (iframe ↔ 부모)요청마다 고유한 id를 부여하고, 응답이 오면 해당 id의 Promise를 resolve하는 방식입니다. // bridge.js (부모 페이지와 iframe 양쪽에서 공유)class IframeBridge { constructor(targetWindow, targetOrigin) { this.targetWindow = targetWindow; this.targetOrigin = targetOrigin; this.pendingRequests = new Map(); this.handlers = new Map(); window.addEventListener('message', (event) => { if (event.origin !== this.targetOrigin) return; this._handleMessage(event.data); }); } // 메시지 전송 후 응답을 Promise로 반환 request(type, payload) { return new Promise((resolve, reject) => { const id = crypto.randomUUID(); this.pendingRequests.set(id, { resolve, reject }); this.targetWindow.postMessage( { id, type, payload }, this.targetOrigin ); // 타임아웃 처리 (5초) setTimeout(() => { if (this.pendingRequests.has(id)) { this.pendingRequests.delete(id); reject(new Error(`Request timeout: ${type}`)); } }, 5000); }); } // 특정 메시지 타입에 핸들러 등록 on(type, handler) { this.handlers.set(type, handler); } _handleMessage(message) { const { id, type, payload, error, isResponse } = message; // 응답 메시지 처리 if (isResponse && this.pendingRequests.has(id)) { const { resolve, reject } = this.pendingRequests.get(id); this.pendingRequests.delete(id); error ? reject(new Error(error)) : resolve(payload); return; } // 요청 메시지 처리 → 핸들러 실행 후 응답 전송 const handler = this.handlers.get(type); if (handler) { Promise.resolve() .then(() => handler(payload)) .then((result) => { this.targetWindow.postMessage( { id, type, payload: result, isResponse: true }, this.targetOrigin ); }) .catch((err) => { this.targetWindow.postMessage( { id, type, error: err.message, isResponse: true }, this.targetOrigin ); }); } }} 부모 페이지에서 사용

0

QueryDSL - OpenFeign Querydsl 6.12의 builder 기반 @QueryProjection

목차 QueryDSL - OpenFeign Querydsl 6.12의 builder 기반 @QueryProjection QueryDSL - QueryDsl 페이징 사용하기 QueryDSL - 사용자 정의 Repository QueryDSL - 동적 쿼리와 성능 최적화 조회 QueryDSL - 순수 JPA 리포지토리와 Querydsl QueryDSL - SQL Function 호출하기 QueryDSL - 수정, 삭제 벌크 연산 QueryDSL - 동적 쿼리 QueryDSL - 프로젝션과 결과 반환 QueryDSL - 조인 QueryDSL - 집계 QueryDSL - 결과 조회 QueryDSL - 검색 조건 쿼리 QueryDSL - Q-Type 활용 QueryDSL - JPA JPQL vs JPA QueryDSL QueryDSL 시작하기 기존 @QueryProjection의 문제@QueryProjection 은 DTO 생성자에 붙이면 해당 DTO용 Q 타입을 생성해 주는 기능이다. Projections.constructor 와 달리 컴파일 타임에 타입을 체크해 주기 때문에 더 안전하다. // DTOpublic class MemberSummaryDto { private final Long memberId; private final String username; private final Integer age; private final String teamName; @QueryProjection public MemberSummaryDto(Long memberId, String username, Integer age, String teamName) { this.memberId = memberId; this.username = username; this.age = age; this.teamName = teamName; }} // 쿼리 사용List<MemberSummaryDto> result = queryFactory .select(new QMemberSummaryDto( member.id, member.username, member.age, team.name )) .from(member) .leftJoin(member.team, team) .fetch(); 파라미터가 적을 때는 문제가 없지만, 필드가 늘어날수록 다음 문제가 생긴다. 파라미터가 많아지면 어떤 인자가 어느 필드로 가는지 한눈에 파악하기 어렵다. Long, String, Integer 처럼 같은 타입의 파라미터가 여럿 있으면 순서를 바꿔도 컴파일 에러가 나지 않는다. 생성자 오버로드가 여러 개일 때 new QMemberSummaryDto(...) 호출부만 보고는 어느 오버로드를 사용하는지 즉시 파악하기 어렵다. Builder 기반 @QueryProjection

0

Agentic RAG 개념 정리

Agentic RAG란Agentic RAG는 검색(retrieval)을 고정된 단계로 실행하는 대신, 에이전트가 상황에 따라 검색 여부와 검색 방식을 스스로 결정하는 RAG 방식입니다. 기본 RAG는 보통 아래처럼 동작합니다. 사용자 질문 입력 검색 시스템에서 관련 문서 조회 검색 결과를 LLM에 넣어 답변 생성 즉, 검색은 항상 먼저 일어나고, 생성은 그 다음에 일어납니다. 반면 Agentic RAG는 조금 다릅니다. 질문만 보고 바로 답할 수도 있음 먼저 검색한 뒤 추가 검색이 필요하다고 판단할 수도 있음 내부 문서 검색과 웹 검색을 나눠서 사용할 수도 있음 한 번 검색하고 끝내지 않고, 여러 번 검색/정제/도구 호출을 반복할 수도 있음 즉, Agentic RAG의 핵심은 “RAG에 agent의 의사결정 능력을 붙인 것” 입니다. 왜 필요한가

0

Corrective RAG (CRAG) 개념 정리

Corrective RAG란Corrective RAG(CRAG)는 기존 RAG 파이프라인에 “검색 결과 검증 및 보정 단계”를 추가한 방식입니다. 일반적인 RAG는 아래 흐름으로 동작합니다. 사용자 질문 입력 벡터 DB 또는 검색 시스템에서 관련 문서 검색 검색된 문서를 컨텍스트로 LLM이 답변 생성 문제는 검색 결과가 부정확하거나, 애매하거나, 일부만 맞는 경우에도 그대로 답변 생성에 사용된다는 점입니다.즉, RAG는 hallucination을 줄여주지만, retrieval 자체가 잘못되면 오히려 잘못된 문서를 근거로 그럴듯한 답을 만들 수 있습니다. CRAG는 이 문제를 해결하기 위해, 검색 결과의 품질을 먼저 평가한 뒤 결과를 그대로 쓸지, 보정할지, 외부 검색으로 보강할지 결정합니다. 왜 필요한가기본 RAG가 자주 겪는 문제는 아래와 같습니다. 질문과 겉보기로만 비슷한 문서를 가져옴 문서 일부는 맞지만 핵심 답변에는 부족함 내부 지식베이스에 최신 정보가 없음 irrelevant chunk가 섞여 답변 품질이 흔들림