-
Notifications
You must be signed in to change notification settings - Fork 0
Reactive Programming ‐ Core Operators in WebFlux Reactor
woojin.jang edited this page May 21, 2026
·
5 revisions
// 자바 record를 활용해 객체 스트림을 생성한 뒤, map을 통해 특정 필드를 추출
public class Main {
public static void main(String[] args) {
// map
Flux.just(
new User("홍길동", 30),
new User("김영희", 25),
new User("이철수", 35)
)
.map(User::name)
.subscribe(name -> System.out.println(name));
}
record User(String name, int age) {
}
}// 숫자 형태 문자열을 정수로 변환하다가 파싱 불가능한 경우 에러 이벤트(onError)가 발생해 스트림이 다운스트림으로 어떻게 시그널을 전파하는지?
public class Main {
public static void main(String[] args) {
// map
Flux.just("1", "2", "abc", "4")
.map(s -> Integer.parseInt(s))
.subscribe(
data -> System.out.println("변환 성공 " + data),
error -> System.out.println("변환 실패 " + error.getMessage())
);
}
}-
onError는 종료 신호이다. -
onError는onComplete와 마찬가지로 해당 스트림의 최종 종료를 의미하는데 예외를 캐치해 하위 파이프라인으로onError(Throwable)신호를 던지고 파이프라인을 즉시 폐쇄(Terminate)한다. - 따라서 파이프라인 내부의 전원 스위치가 내려간 것과 같기 때문에 뒤에 남아있던 데이터들(예를 들어, "4")은 평가조차 이루어지지 못하고 드롭된다.
public class Main {
public static void main(String[] args) {
// flatMap
long start = System.currentTimeMillis();
Flux.just("A", "B", "C")
.flatMap(letter -> Mono.just(letter).delayElement(Duration.ofSeconds(1)))
.map(l -> l + "처리 완료")
.subscribe(
data -> {
long elapsed = System.currentTimeMillis() - start;
System.out.println(data + " - " + elapsed + "ms");
}
);
try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
}
}-
map은 스트림 내부에 또 다른 스트림이 겹쳐진 중첩 구조가 되지만flatMap은 내부의Mono/Flux들을 하나로 이어 붙여서 평평한 하나의 스트림으로 펴주는 역할을 한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// concatMap
Flux.just("느린", "빠른", "중간").flatMap(task -> {
long delay = switch (task) {
case "느린" -> 300;
case "빠른" -> 100;
case "중간" -> 200;
default -> 0;
};
return Mono.just(task).delayElement(Duration.ofMillis(delay));
}).subscribe(System.out::println);
long start = System.currentTimeMillis();
Flux.just("느린", "빠른", "중간").concatMap(task -> {
long delay = switch (task) {
case "느린" -> 300;
case "빠른" -> 100;
case "중간" -> 200;
default -> 0;
};
return Mono.just(task).delayElement(Duration.ofMillis(delay));
}).subscribe(data -> {
long elap = System.currentTimeMillis() - start;
System.out.println(elap + "ms: " + data);
});
Thread.sleep(1000);
}
}-
concatMap()은 무조건 들어온 순서대로 하나씩 차례대로 처리한다. - 만약 A, B, C가 순서대로 들어오면, A의 비동기 처리가 끝날 때까지 B, C는 시작도 하지 않고 기다린다.
- 비동기 처리 속도가 제각각이더라도 최종 결과물은 반드시 A ➔ B ➔ C라는 원래의 순서가 엄격하게 보장된다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// flatMapSequential은 flatMap과 달리 순서를 보장한다.
long start = System.currentTimeMillis();
Flux.just("느린", "빠른", "중간")
.flatMapSequential(task -> {
long delay = switch (task) {
case "느린" -> 300L;
case "빠른" -> 100L;
case "중간" -> 200L;
default -> 0L;
};
return Mono.just(task).delayElement(Duration.ofMillis(delay));
})
.subscribe(data -> {
long elapsed = System.currentTimeMillis() - start;
System.out.println(elapsed + "ms: " + data);
});
Thread.sleep(500);
}
}-
flatMapSequential은 비동기로 동시에 쏘면서 성능을 챙기면서도 최종 결과물의 순서는 원본 순서대로 정렬한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// flatMapMany: Mono 환경에서 다중 데이터(Flux) 스트림으로 확장 및 평탄화
// 1. 단순 문자열을 분해하여 여러 개의 데이터로 밀어내기
Mono.just("hello")
.flatMapMany(word -> Flux.fromArray(word.split("")))
.subscribe(System.out::println);
// 2. 실무 정석: 단일 조건(Mono)으로 다중 데이터 조회(Flux) 파이프라인 연동
Mono.just("user123")
.flatMapMany(Main::findOrdersByUserId)
.subscribe(System.out::println);
}
static Flux<String> findOrdersByUserId(String userId) {
return Flux.just("주문#1001", "주문#1002", "주문#1003");
}
}-
flatMapMany는Mono를Flux로 전환하기 위해 사용하는 연산자이다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// filter 복합 체이닝
Flux.just(
new Product("노트북", 1500000, true),
new Product("마우스", 35000, true),
new Product("키보드", 89000, false),
new Product("모니터", 450000, true),
new Product("웹캠", 62000, true)
)
.filter(Product::inStock) // 1차 필터: 재고 유무 확인
.filter(p -> p.price() <= 100000) // 2차 필터: 가격 조건 확인
.subscribe(p -> System.out.println(p.name() + " - " + p.price() + "원"));
}
record Product(String name, int price, boolean inStock) {
}
}-
filter()는 내부에 진위 연산 람다식을 받아서 스트림의 흐름을 제어한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// 1. 기본 distinct: 객체 자체의 equals/hashCode 기반 중복 제거
Flux.just(1, 2, 3, 2, 1, 4, 3, 5)
.distinct()
.subscribe(data -> System.out.print(data + " "));
System.out.println();
// 2. 키 기반 distinct: 특정 필드(Key) 기준으로 중복 제거
Flux.just(
new User("홍길동", "서울"),
new User("김영희", "부산"),
new User("이철수", "서울"),
new User("박지민", "대전"),
new User("최민수", "부산")
)
.distinct(User::city) // 각 유저의 city 필드를 기준으로 중복 판별
.subscribe(user -> System.out.println(user.name() + " (" + user.city() + ")"));
}
record User(String name, String city) {
}
}-
distinct()는 중복된 데이터를 완벽하게 솎아내는 역할을 한다. - 그러나 대용량 아키텍처 관점에서 치명적인 트레이드 오프를 가지고 있다.
-
메모리 누수 위험 :
Flux.interval처럼 종료되지 않고 무한히 흐르는 스트림이거나 하루에 수억 건씩 쏟아지는 금융 트랜잭션 스트림에 걸게 되면 스트림이 유지되는 내내 내부Set에 데이터가 계속 누적되면서 힙 메모리를 잡아먹게 되고 결국 서버가OutOfMemoryError(OOM)을 뱉을 수 있다. -
대안 -
distinctUntilChanged(): 해당 메서드는 전체 데이터가 아니라 바로 직전에 통과한 데이터와만 비교해 연속으로 중복되는 데이터만 쳐내는 오퍼레이터이다.
-
메모리 누수 위험 :
public class Main {
public static void main(String[] args) throws InterruptedException {
// 1. 특정 인덱스의 데이터 추출 (Zero-based Index)
Flux.just("사과", "바나나", "체리", "딸기")
.elementAt(2)
.subscribe(data -> System.out.println("elementAt: " + data));
// 2. 인덱스 초과 시 기본값(Default Value)으로 방어
Flux.just("사과", "바나나")
.elementAt(5, "없음")
.subscribe(data -> System.out.println("elementAt with default: " + data));
}
}-
elementAt()연산자는 원하는 딱 하나의 데이터만 잡아내는 역할을 한다. - 그러나 기본값 없는 인덱스 초과가 발생할 위험이 높아 치명적인 안티패턴으로 잘 쓰이지 않는다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// Flux.concat: 여러 스트림을 순차적으로 연결
Flux<String> first = Flux.just("A", "B", "C");
Flux<String> second = Flux.just("D", "E", "F");
Flux<String> third = Flux.just("G");
Flux.concat(first, second, third)
.subscribe(data -> System.out.println("Received: " + data));
}
}-
concat()는 리액티브 스트림즈에서 다수의 데이터 소스를 순서대로 이어 붙여 하나의 연속적인 파이프라인으로 만드는 메커니즘을 보여준다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// concatWith: 인스턴스 메서드 체이닝을 활용한 스트림 순차 연결
Flux.just("A", "B")
.concatWith(Flux.just("C", "D"))
.concatWith(Flux.just("E"))
.subscribe(System.out::println);
}
}-
concatWith()는Flux.concat과 내부적인 연결 및 지연 구독 메커니즘과 동일하다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// Flux.merge: 여러 스트림을 동시에 구독하여 인터리빙(Interleaving) 병합
long start = System.currentTimeMillis();
Flux<String> slow = Mono.just("slow")
.delayElement(Duration.ofMillis(500))
.flux();
Flux<String> fast = Mono.just("fast")
.delayElement(Duration.ofMillis(200))
.flux();
Flux.merge(slow, fast)
.subscribe(data -> {
long elapsed = System.currentTimeMillis() - start;
System.out.println(elapsed + "ms: " + data);
});
Thread.sleep(1000);
}
}- 여러 독립된 데이터 소스를 순서에 상관없이 먼저 처리되는 대로 가장 빠르게 다운스트림에 밀어 넣어 병합하는 역할을 한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// Flux.zip: 여러 스트림의 데이터를 index 기준으로 1:1 매칭하여 결합
// 1. Bi-Function 조합식을 결합한 형태 (2개의 스트림)
Flux<String> names = Flux.just("홍길동", "김영희", "이철수");
Flux<Integer> ages = Flux.just(20, 30, 40);
Flux.zip(names, ages, (name, age) -> name + " : " + age)
.subscribe(System.out::println);
System.out.println("--------------------------------");
// 2. 3개 이상의 스트림 결합 (Tuple 데이터 구조 활용)
Flux<String> names2 = Flux.just("홍길동", "김영희", "이철수");
Flux<Integer> ages2 = Flux.just(20, 30, 40);
Flux<String> cities = Flux.just("서울", "부산", "대구");
Flux.zip(names2, ages2, cities)
.map(tuple -> tuple.getT1() + "(" + tuple.getT2() + "세, " + tuple.getT3() + ")")
.subscribe(System.out::println);
System.out.println("--------------------------------");
// 3. 결합할 데이터의 개수가 불일치할 때의 동작 메커니즘
Flux<String> names3 = Flux.just("홍길동", "이철수", "박지민");
Flux<Integer> ages3 = Flux.just(20, 30);
Flux.zip(names3, ages3)
.subscribe(System.out::println);
}
}-
zip()는 서로 다른 파이프라인에서 흐르는 데이터들을 동일한 인덱스 기준으로 정확하게 한 쌍씩 묶어내는 역할을 한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// defaultIfEmpty: 스트림이 비어있을 때 고정된 기본값(Fallback Value)을 반환
// 1. 빈 스트림인 경우 -> 기본값 출력
Flux<String> result = Flux.<String>empty()
.defaultIfEmpty("데이터 없음");
result.subscribe(System.out::println);
System.out.println("--------------------------------");
// 2. 데이터가 존재하는 경우 -> 원본 데이터 그대로 통과
Flux<String> hasData = Flux.just("A", "B", "C")
.defaultIfEmpty("데이터 없음");
hasData.subscribe(System.out::println);
}
}-
defaultIfEmpty()는 데이터가 흐르지 않고 그냥 끝나버린 빈 스트림을 감지하고 이에 대한 디폴트 값을 안전하게 밀어주는 역할을 한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// defaultIfEmpty, switchIfEmpty
Flux<String> fromCache = Flux.empty(); // 캐시에 데이터가 없다고 가정
fromCache
.switchIfEmpty(Flux.defer(() -> findFromDB()))
.subscribe(data -> System.out.println("Received: " + data));
Flux<String> cached = Flux.just("캐시된 데이터 1", "캐시된 데이터 2");
cached
.switchIfEmpty(Flux.defer(() -> findFromDB()))
.subscribe(data -> System.out.println("Received: " + data));
}
private static Flux<String> findFromDB() {
// DB에서 데이터를 조회한다고 가정
return Flux.just("A", "B", "C");
}
}-
switchIfEmpty()는 고정된 값이 아니라 또 다른 리액티브 스트림을 통째로 갈아끼우는 역할을 한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// hasElement & hasElements: 스트림 내 데이터 존재 여부 검증
// 1. Mono에 데이터가 존재하는 경우 -> true
Mono.just("hello")
.hasElement()
.subscribe(has -> System.out.println("Mono(just) has element: " + has));
// 2. Mono가 비어있는 경우 -> false
Mono.empty()
.hasElement()
.subscribe(has -> System.out.println("Mono(empty) has element: " + has));
System.out.println("--------------------------------");
// 3. Flux에 데이터가 존재하는 경우 -> true
Flux.just(1, 2, 3)
.hasElements()
.subscribe(has -> System.out.println("Flux(just) has elements: " + has));
// 4. Flux가 비어있는 경우 -> false
Flux.empty()
.hasElements()
.subscribe(has -> System.out.println("Flux(empty) has elements: " + has));
}
}-
hasElement()는 단일 발행자의 스트림이 비어있는지 여부를 판단하고hasElements()는 다중 발행자의 스트림이 비어있는지 여부를 확인한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// any & all: 스트림 내 데이터의 조건 충족 여부 검증
Flux<Integer> numbers = Flux.just(2, 4, 6, 8, 10);
// 1. any: 하나라도 조건을 만족하는가?
numbers.any(n -> n > 7)
.subscribe(has -> System.out.println("any (n > 7): " + has));
numbers.any(n -> n > 100)
.subscribe(has -> System.out.println("any (n > 100): " + has));
System.out.println("--------------------------------");
// 2. all: 모든 데이터가 조건을 만족하는가?
numbers.all(n -> n % 2 == 0)
.subscribe(has -> System.out.println("all (n % 2 == 0): " + has));
numbers.all(n -> n > 3)
.subscribe(has -> System.out.println("all (n > 3): " + has));
}
}-
any()는 스트림에 흘러가는 데이터 집합이 특정 비즈니스 조건에 맞는지 판별해 하나라도 조건을 만족하면true를 리턴한다. -
all()는 스트림에 흘러가는 데이터의 집합이 전체 조건을 만족하면true를 반환한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// 1. then(Mono): 원본 스트림 완료 후 다른 단수(Mono) 스트림 실행 (원본 데이터는 드롭)
Flux.just("저장1", "저장2", "저장3")
.doOnNext(data -> System.out.println("처리 중: " + data))
.then(Mono.just("모든 저장 완료"))
.subscribe(result -> System.out.println("결과: " + result));
System.out.println("---");
// 2. then(): 인자 없이 호출하여 성공 종료 신호(Mono<Void>)만 하위로 전달
Flux.just("작업1", "작업2")
.doOnNext(data -> System.out.println("실행: " + data))
.then()
.subscribe(
data -> {},
error -> {},
() -> System.out.println("모든 작업 완료")
);
System.out.println("---");
// 3. thenMany(Flux): 원본 스트림 완료 후 다른 다중(Flux) 스트림 실행
Flux.just("초기화1", "초기화2")
.doOnNext(data -> System.out.println("초기화: " + data))
.thenMany(Flux.just("결과 A", "결과 B", "결과 C"))
.subscribe(data -> System.out.println("수신: " + data));
}
}-
then()는 앞선 스트림이 성공적으로 끝나면 기존 데이터들은 메모리에서 전부 폐기하고 인자로 넘겨받은 단수 발행자로 파이프라인을 갈아 끼운다. -
thenMany()는 기존 데이터를 버리는 메커니즘은then()과 동일하나onComplete를 호출하는 순간 파이프라인이Flux<T>스트림으로 변환된다.
import java.util.HashMap;
import reactor.core.publisher.Flux;
public class Main {
public static void main(String[] args) throws InterruptedException {
// reduce: 스트림의 모든 원소를 하나의 단일 값으로 압축(축소)하는 연산자
// 1. 초기값 없는 reduce: 첫 번째 데이터가 초기값이 됨
Flux.just(1, 2, 3, 4, 5)
.reduce((acc, next) -> acc + next)
.subscribe(result -> System.out.println("결과 (합산): " + result));
Flux.just("Hello", "Reactor", "World")
.reduce((acc, next) -> acc + next)
.subscribe(result -> System.out.println("결과 (문자열): " + result));
System.out.println("---------");
// 2. 초기값(Seed)이 있는 reduce: 빈 스트림이 들어와도 초기값을 안전하게 반환
Flux.just(1, 2, 3, 4, 5)
.reduce(100, (acc, next) -> acc + next)
.subscribe(result -> System.out.println("결과 (초기값 100): " + result));
Flux.<Integer>empty()
.reduce(100, (acc, next) -> acc + next)
.subscribe(result -> System.out.println("결과 (빈 스트림 방어): " + result));
System.out.println("---------");
// 3. 가변 객체(Map)를 컬렉터로 활용한 데이터 집계 및 빈도수(Frequency) 카운팅
Flux.just("apple", "banana", "apple", "cherry", "banana", "cherry")
.reduce(new HashMap<String, Integer>(), (map, word) -> {
map.merge(word, 1, Integer::sum);
return map;
})
.subscribe(freq -> System.out.println("결과 (빈도수): " + freq));
}
}-
reduce(): 중간 과정은 숨기고 결과만 중요할 때 선택한다. -
scan(): 중간 과정을 실시간 스트림(Flux)으로 내어줄 때 선택한다.
import reactor.core.publisher.Flux;
public class Main {
public static void main(String[] args) throws InterruptedException {
// groupBy: 특정 조건(Key)을 기준으로 하나의 스트림을 여러 개의 서브 스트림으로 분할
Flux.range(1, 10)
.groupBy(n -> n % 2 == 0 ? "짝수" : "홀수") // 1. 그룹화 기준 키 정의
.flatMap(group -> group.collectList().map(list -> group.key() + " : " + list)) // 2. 그룹별 데이터 수집
.subscribe(System.out::println);
}
}-
groupBy()는 자바 애플리케이션 서버의 메모리 레벨에서 실시간으로 스트림을 쪼개는 그룹핑 역할을 수행한다.
- Java - Class
- Java - Java Memory⭐
- Java ‐ Solving Concurrency Issues with Synchronized
- Java - synchronized
- Java ‐ Instance Variable & Local Variable vs final
- Java ‐ Object
- Java ‐ Immutable Object
- Java ‐ String
- Java ‐ Wrapper Class
- Java ‐ ENUM
- Java ‐ Nested Class & Inner Class & Local Class & Anonymous Class
- Java ‐ Generic
- Java ‐ ArrayList
- Java ‐ LinkedList
- Java ‐ List
- Java ‐ Set
- Java ‐ Hash
- Java ‐ HashSet
- Java ‐ Map & Stack & Queue
- Java ‐ Iterate & Sort
- Java - Process & Thread
- Java - Thread Creation & Execution
- Java - Thread Control & LifeCycle
- Java - Memory Visibility
- Java - Advanced Synchronization
- Java - Producer/Consumer Problem
- Java - Synchronization & Atomic Operation
- Java - Concurrent Collection
- Java - Thread Pool & Executor Framework
- Java - Character Encoding
- Java - I/O
- Java - File & Files
- Java - Reflection
- Java - Annotation
- Java - Lambda
- Java - Functional Interface
- Java - Lambda vs Anonymous Class
- Java - Method Reference
- Java - Stream API
- Java - Optional
- Java - Default Method
- Java - Parallel Stream
- Java - Functional Programming
- Java - JVM & GC & SOLID⭐
- Java - Data Storage and Memory Allocation: Primitive vs. Reference
- Java ‐ Static Keyword: Efficient Resource Management at the Class Level
- Java ‐ OOP
- Java ‐ Collection Framework Selection Standard
- Java ‐ Multi Threading & Concurrent Programming
- Java ‐ Exception Handling & Advanced Java
- Java ‐ Java 8+
- Java ‐ Java Application Performance Tuning
- Java - CAS
- Java - Virtual Thread⭐
- Kotlin - Variables, Types, and Operators in Kotlin
- Kotlin - Control Flow in Kotlin
- Kotlin - Object-Oriented Programming in Kotlin
- Kotlin - Functional Programming in Kotlin
- Kotlin - Key Features of Kotlin
- Kotlin - Generics in Kotlin
- Kotlin - Lazy Initialization and Delegation in Kotlin
- Kotlin - Advanced Functional Programming in Kotlin
- Kotlin - Operator Overloading and Kotlin DSL
- Kotlin - Annotations and Reflection in Kotlin
- Kotlin - Miscellaneous Topics in Kotlin
- Coroutine - Limitations of Thread-Based Work & the Emergence of Coroutines
- Coroutine - runBlocking
- Coroutine - CoroutineDispatcher
- Coroutine - Controlling Coroutines with Job
- Coroutine - Receiving Results from Coroutines
- Coroutine - Coroutine Context
- Coroutine - Structured Concurrency
- Coroutine - Exception Handling
- Coroutine - Suspended Functions
- Coroutine - Understanding Coroutines
- Coroutine - Advanced Coroutines
- Coroutine - Coroutine Testing
- Spring - OOP & Spring
- Spring - Spring Container & Spring Bean
- Spring - Singleton Container
- Spring - Dependency Injection
- Spring - Bean LifeCycle Callback
- Spring - Bean Scope
- Spring ‐ Web Server, Web Application server
- Spring ‐ Servlet
- Spring ‐ Servlet & JSP & MVC
- Spring ‐ MVC Framework
- Spring ‐ Spring MVC
- Spring - Thymeleaf
- Spring - Message & Internationalization
- Spring - Validation
- Spring - Bean Validation
- Spring - Cookie & Session
- Spring - Filter & Interceptor
- Spring - API Exception Handling
- Spring - Spring Type Converter
- Spring - File Upload
- Spring - Connection Pool & DataSource⭐
- Spring - Transaction⭐
- Spring ‐ Spring Exception Abstraction
- Spring - Database Access
- Spring ‐ Spring Transaction⭐
- Spring ‐ Spring Transaction Propagation⭐
- Spring ‐ Thread Local
- Spring ‐ Template Method Pattern & Callback Pattern
- Spring ‐ Dynamic Proxy
- Spring ‐ Spring Proxy⭐
- Spring ‐ Bean Processor
- Spring ‐ @Aspect AOP
- Spring ‐ Spring AOP
- Spring ‐ Spring AOP Application
- Spring - MyBatis
- Spring ‐ URL Encoding
- Spring - Cache Annotation
- Spring - Retry
- Spring Security - Initialization⭐
- Spring Security - Authentication Process
- Spring Security - Authentication Architecture
- Spring Security - Authentication Status Persistence Processing Mechanism
- Spring Security - Session Management
- Spring Security - Exception Handling
- Spring Security - Mechanisms for responding to Malicious Attacks
- Spring Security - Authorization Process
- Spring Security - Authorization Architecture
- Spring Security - Multiple Security Settings
- Spring Security - Redis Redundancy Settings
- Spring Security - Event
- Spring Security - Integration
- Spring Security - OAuth 2.0
- Spring Security - OAuth 2.0 Authorization Type
- Spring Security - Open ID Connect
- Spring Security - OAuth 2.0 Client
- Spring Security - OAuth 2.0 Client Fundamentals
- Spring Security - OAuth 2.0 oauth2Login
- Spring Security - OAuth 2.0 oauth2Client
- Spring Security - OAuth 2.0 Resource Server
- Spring Security - OAuth 2.0 Resource Server API
- Spring Security - OAuth 2.0 Verification
- Spring Security - OAuth 2.0 MAC & RSA Token Verification
- Spring Security - OAuth 2.0 Resource Server Permission Implementation
- Spring Security - OAuth 2.0 opaque()
- Spring Security - Authorization Server
- Spring Security - Authorization Server Main Domain Class
- Spring Security - Authorization Server Endpoint Protocol
- Spring Batch - Scheduler vs Batch
- Spring Batch - Batch Concept
- Spring Batch - Batch Domain
- Spring Batch - Job
- Spring Batch - Step
- Spring Batch - Flow
- Spring Batch - Chunk Process
- Spring Batch - ItemReader
- Spring Batch - ItemWriter
- Spring Batch - ItemProcessor
- Spring Batch - Retry & Error Handling
- Spring Batch - Multi Threads Processing
- [Spring Batch - Batch Event Listener]
- [Spring Batch - Batch Test]
- [Spring Batch - File Processing]
- [Spring Batch - Read and Write Operations in Relational Databases and NoSQL]
- [Spring Batch - FaultTolerant & ItemStream]
- [Spring Batch - Partitioning]
- Reactive Programming - Reactive System & Reactive Programming
- Reactive Programming - Fundamentals of WebFlux and Reactor
- Reactive Programming - Core Operators in WebFlux Reactor
- Reactive Programming - Practical Patterns in WebFlux
- Reactive Programming - WebFlux Patterns with Spring Boot
- Database - Database Introduction
- Database - Search & Sort
- Database - Data Processing
- Database - Aggregation & Grouping
- Database - Inner Join
- Database - Outer Join & Etc Join
- Database - Sub Query
- Database - UNION
- Database - CASE
- Database - Index
- Database - Data Integrity
- Database - Transaction
- Database - Why Database Design Matters
- Database - Concept Modeling
- Database - Logical Data Modeling
- Database - Identifying Relationship & Non-Identifying Relationship
- Database - Normalization
- Database - Physical Data Modeling
- Database - Common Code Design
- Database - Hierarchical Structure Design
- Database - Data Change History Design
- Database - SOFT DELETE
- Database - Statistics Table Design
- Database - Inheritance Relationship Design
- Database - Entity-Attribute-Value (EAV) Model
- Database - JSON Schema Design
- MySQL ‐ Solving Concurrency Problems using Database-Level Locking
- MySQL - Checking DB Metrics with SQL Queries
- MySQL - Data Modeling for Practical Service Development
- MySQL - Basic CRUD in MySQL
- MySQL - MySQL Horizontal Scaling
- MySQL - MySQL Fundamentals
- MySQL - Why You Should Use MySQL: JOIN
- MySQL - Must-Know SQL Anti-Patterns
- MySQL - Learning Data Modeling Through Practical Examples
- MySQL - Foreign Key & Strategic Patterns
- MySQL - Advanced Topics in MySQL
- MySQL - Multi Column Index
- MySQL - Covering Index
- MySQL - ORDER BY
- MySQL - INSERT
- MySQL - AUTO_INCREMENT_LOCK
- MySQL - MySQL LockType
- MySQL - DeadLock Case
- MySQL - NoOffset For Query Tuning
- Redis ‐ Redis
- Redis ‐ Redis Manual
- Redis ‐ Redis Cache Strategy
- Redis ‐ Redis Master-Slave
- Redis - Redis Cluster Mode
- Redis - Redis Cluster Example
- Redis - Redis Data Structure
- Redis - Redis pub/sub & streams
- Redis - Redis Server
- Redis - Reduce DB write load using Redis
- Redis - Solving Concurrency Issues (1)
- Redis ‐ Solving Concurrency Issues (2)
- Redis - Solving Concurrency Issues (3)
- Redis - Implementing Popular Searches
- Redis - API Rate Limiting
- Redis - Geospatial
- Redis - DAU Counting Application
- Redis - Session Management Application
- Redis - Redis Transaction ACID
- Redis - Redis Data Persistence
- Redis - Redis Keys Management
- Redis - Decoupling microservices with Redis Pub/Sub
- Redis - Redis Pipelining & RTT(Round Trip Time)
- Redis - Redis Streams
- Redis - Hash Slot Rebalancing
- JPA - Java Persistence API
- JPA - Entity Mapping & PK Strategy
- JPA ‐ JPA Association Mapping
- JPA - Proxy Association
- JPA - Value Type
- JPA - Dirty Checking vs. Merge: Understanding the Difference in JPA
- JPA - Cascading and Orphan Removal in JPA
- JPA - Introduction to Object-Oriented Query Languages in JPA
- JPA - Spring Data JPA
- JPA ‐ Solving Concurrency Issues with Pessimistic Locking
- JPA ‐ Solving Concurrency Issues with Optimistic Locking
- JPA - Lazy Loading and Performance Optimization in JPA
- JPA - ManyToOne Important Things
- JPA - OneToMany Important Things
- JPA - OSIV
- MicroService Architecture - DeComposition Patterns
- MicroService Architecture - Service Communications Patterns
- MicroService Architecture - API Gateway Patterns
- MicroService Architecture - Asynchronous Communications Patterns
- MicroService Architecture - Data Management Patterns
- MicroService Architecture - CQRS Patterns
- MicroService Architecture - Distributed Transactions
- [MicroService Architecture - Event-Driven Architecture]
- [MicroService Architecture - Resilience & Observability and Monitoring]
- [MicroService Architecture - Security Patterns]
- [MicroService Architecture - Testing Strategies]
- [MicroService Architecture - Scalability & Caching Patterns]
- [MicroService Architecture - Deployment Patterns]
- [MicroService Architecture - Serverless Architecture]
- [MicroService Architecture - GraphQL]
- [MicroService Architecture - Evolution of Distributed Systems and Their Drawbacks]
- [MicroService Architecture - Protocol Buffers]
- [MicroService Architecture - gRPC Communication Patterns]
- [MicroService Architecture - gRPC Optimization Strategies and Implementation]
- MicroService Architecture - 2PC
- MicroService Architecture - TCC
- MicroService Architecture - SAGA
- Apache Kafka - Kafka Introduction
- Apache Kafka - Kafka CLI
- Apache Kafka - Kafka Producer Application
- Apache Kafka - Kafka Consumer Application
- Apache Kafka - Idempotent Producer & Transactional Producer & Transactional Consumer
- Apache Kafka - Kafka Streams
- Apache Kafka - Kafka Topic/Producer/Consumer
- Apache Kafka - Producer Mechanism
- Apache Kafka - Consumer Mechanism
- Apache Kafka - Multi Node Kafka Cluster
- Apache Kafka - Producer & Consumer Serialization/DeSerialization
- Apache Kafka - Topic Segment Management
- Apache Kafka - KSQLDB Stream
- Apache Kafka - KSQLDB Table
- Apache Kafka - KSQLDB Application
- Apache Kafka - Group by & Mview
- [Apache Kafka - Join]
- [Apache Kafka - Time & Windows]
- [Apache Kafka - Connecting KSQLDB to Kafka Connect]
- [Apache Kafka - Kafka Connect]
- [Apache Kafka - JDBC Source Connector]
- [Apache Kafka - JDBC Sink Connector]
- [Apache Kafka - Debezium MySQL CDC Source Connector]
- [Apache Kafka - Schema Registry]
- Apache Kafka - Differences Between RocksDB and In-Memory KeyValueStore in GlobalKTable
- Apache Kafka - Kafka Streams
- [Apache Kafka - Kafka Connect]
- [Apache Kafka - Idempotent Producers and Transactional Producers & Consumers]
- [Apache Kafka - CDC(Change Data Capture)]
- [Apache Flink - Apache Flink Architecture]
- [Apache Flink - Stream Processing]
- [Apache Flink - Data Stream API & Window]
- [Apache Flink - State Management]
- HTTP - Internet Network
- HTTP - URI & Browser Request Flow
- HTTP - HTTP Basic
- HTTP - HTTP Method
- HTTP - HTTP Method Application
- HTTP - HTTP Status Code
- HTTP ‐ HTTP Default Header
- HTTP - HTTP Cache & Condition Request
- AWS - AWS CDK(Cloud Development Kit)
- AWS - Signed URL
- AWS - PreSigned URL
- AWS - Cognito
- AWS - Signed URL Logic
- Docker - Docker
- Docker ‐ Docker CLI
- Docker ‐ Docker Volume
- Docker - Dockerfile Image
- Docker ‐ Docker Compose Container Management
- Docker ‐ Deploy(feat. AWS ECR)
- Docker - Cloud Native Technology
- Docker - Docker Essentials(1)
- Docker - Docker Essentials(2)
- Docker - Docker Network and Storage
- Docker - Building and Managing Containerized Application
- Docker - Container Orchestration
- Docker - Docker Security
- Docker - Logging and Monitoring
- Docker - Advanced Docker Usage
- Docker - Container-to-Container Communication
- Kubernetes - Probe
- Kubernetes - ConfigMap & Secret
- Kubernetes - PV/PVC & Deployment & Service & HPA
- Kubernetes - Helm & Kustomize
- Kubernetes - Pod 1
- [Kubernetes - Pod 2]
- Kubernetes - Controller 1
- [Kubernetes - Controller 2]
- [Kubernetes - Object]
- [Kubernetes - Ingress & Nginx Application]
- [Kubernetes - Node Scheduling]
- [Kubernetes - Monitoring]
- [Kubernetes - Logging]
- Kubernetes - Deployment using Amazon EKS
- Nginx ‐ Nginx Introduction
- Nginx ‐ Nginx Supplementary Summary
- Nginx ‐ Deploying Domain with Nginx
- Nginx ‐ Implementing HTTPS with Nginx
- Nginx ‐ Backend Deployment via Nginx Reverse Proxy
- Nginx ‐ Load Balancing with Nginx
- [Nginx - Advanced Concept]
- [Nginx - Advanced Reverse Proxy]
- [Monitoring - Log Concept]
- [Monitoring - Log Level & Filter]
- [Monitoring - Logback]
- [Monitoring - Log Collection with ELK Stack]
- [Monitoring - Log Monitoring with Kibana]
- [Monitoring - Building a Monitoring System with Spring Boot Actuator]
- [Monitoring - Server Monitoring with Prometheus and Grafana with Discord Alerts]
- Test - Load Testing Fundamentals
- [Test - Diagnosing Bottlenecks via Load Testing]
- [Test - Performance Tuning: Resolving Bottlenecks]
- Test - JUnit5
- Test - Mockito
- Test - TestContainers
- Test - JMeter
- Test - Chaos Monkey
- [Test - ArchUnit]
- [Test - Unit Testing Essentials]
- [Test - TDD]
- [Test - Testing with Spring & JPA]
- Test - A Guide to Effective Mocking
- Test - Appendix: Tips for Better Testing
- (Effective Java Item 1) Java ‐ 생성자 대신 정적 팩토리 메서드를 고려하라
- (Effective Java Item 2) Java - 생성자에 매개변수가 많다면 빌더를 고려하라
- (Effective Java Item 3) Java - private 생성자나 열거 타입으로 싱글턴임을 보증하라
- (Effective Java Item 4) Java - 인스턴스화를 막으려거든 private 생성자를 사용하라
- (Effective Java Item 5) Java - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
- (Effective Java Item 6) Java ‐ 불필요한 객체 생성을 피하라
- (Effective Java Item 7) Java - 다 쓴 객체 참조를 해제하라
- (Effective Java Item 8) Java - finalizer와 cleaner 사용을 피하라
- (Effective Java Item 9) Java - try‐finally보다는 try‐with‐resources를 사용하라
- (Effective Java Item 10) Java ‐ equals는 일반 규약을 지켜 재정의하라
- (Effective Java Item 11) Java ‐ equals를 재정의하려거든 hashCode도 재정의하라
- (Effective Java Item 12) Java - toString을 항상 재정의하라
- (Effective Java Item 13) Java ‐ clone 재정의는 주의해서 진행하라
- (Effective Java Item 14) Java ‐ Comparable을 구현할지 고려하라
- (Effective Java Item 15) Java ‐ 클래스와 멤버의 접근 권한을 최소화하라
- (Effective Java Item 16) Java ‐ public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
- (Effective Java Item 17) Java ‐ 변경 가능성을 최소화하라
- (Effective Java Item 18) Java ‐ 상속보다는 컴포지션을 사용하라
- (Effective Java Item 19) Java ‐ 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라
- (Effective Java Item 20) Java ‐ 추상 클래스보다는 인터페이스를 우선하라
- (Effective Java Item 21) Java ‐ 인터페이스는 구현하는 쪽을 생각해 설계하라
- (Effective Java Item 22) Java ‐ 인터페이스는 타입을 정의하는 용도로만 사용하라
- (Effective Java Item 23) Java ‐ 태그 달린 클래스보다는 클래스 계층구조를 활용하라
- (Effective Java Item 24) Java ‐ 멤버 클래스는 되도록 static으로 만들라
- (Effective Java Item 25) Java ‐ 톱레벨 클래스는 한 파일에 하나만 담으라
- (Effective Java Item 26) Java ‐ 로 타입은 사용하지 말라
- (Effective Java Item 27) Java ‐ 비검사 경고를 제거하라
- (Effective Java Item 28) Java ‐ 배열보다는 리스트를 사용하라
- (Effective Java Item 29) Java ‐ 이왕이면 제네릭 타입으로 만들라
- (Effective Java Item 30) Java ‐ 이왕이면 제네릭 메서드로 만들라
- (Effective Java Item 31) Java - 한정적 와일드카드를 사용해 API 유연성을 높이라
- (Effective Java Item 32) Java - 제네릭과 가변인수를 함께 쓸 때는 신중하라
- (Effective Java Item 33) Java ‐ 타입 안전 이종 컨테이너를 고려하라
- (Effective Java Item 34) Java - int 상수 대신 열거 타입을 사용하라
- (Effective Java Item 35) Java - ordinal 메서드 대신 인스턴스 필드를 사용하라
- (Effective Java Item 36) Java ‐ 비트 필드 대신 EnumSet을 사용하라
- (Effective Java Item 37) Java ‐ ordinal 인덱싱 대신 EnumMap을 사용하라
- (Effective Java Item 38) Java ‐ 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
- (Effective Java Item 39) Java ‐ 명명 패턴보다 애너테이션을 사용하라[Effective Java Item 39]
- (Effective Java Item 40) Java ‐ @Override 어노테이션을 일괄되게 사용하라
- (Effective Java Item 41) Java ‐ 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
- (Effective Java Item 42) Java ‐ 익명 클래스보다는 람다를 사용하라
- (Effective Java Item 43) Java ‐ 람다보다는 메서드 참조를 사용하라
- (Effective Java Item 44) Java - 표준 함수형 인터페이스를 사용하라
- (Effective Java Item 45) Java - 스트림은 주의해서 사용하라
- (Effective Java Item 46) Java - 스트림에서는 부작용 없는 함수를 사용하라
- (Effective Java Item 47) Java - 반환 타입으로는 스트림보다 컬렉션이 낫다
- (Effective Java Item 48) Java ‐ 스트림 병렬화는 주의해서 사용하라
- (Effective Java Item 49) Java ‐ 매개변수가 유효한지 검사하라
- (Effective Java Item 50) Java ‐ 적시에 방어적 복사본을 만들라
- (Effective Java Item 51) Java ‐ 메서드 시그니처를 신중히 설계하라
- (Effective Java Item 52) Java ‐ 다중정의는 신중히 사용하라
- (Effective Java Item 53) Java ‐ 가변인수는 신중히 사용하라
- (Effective Java Item 54) Java - null이 아닌, 빈 컬렉션이나 배열을 반환하라
- (Effective Java Item 55) Java ‐ 옵셔널 반환은 신중히 하라
- (Effective Java Item 56) Java ‐ 공개된 API 요소에는 항상 문서화 주석을 사용하라
- (Effective Java Item 57) Java ‐ 지역변수의 범위를 최소화하라
- (Effective Java Item 58) Java ‐ 전통적인 for문보다는 for‐each문을 사용하라
- (Effective Java Item 59) Java ‐ 라이브러리를 익히고 사용하라
- (Effective Java Item 60) Java ‐ 정확한 답이 필요하다면 float와 double은 피하라
- (Effective Java Item 61) Java ‐ 박싱된 기본 타입보다는 기본 타입을 사용하라
- (Effective Java Item 62) Java ‐ 다른 타입이 적절하다면 문자열 사용을 피하라
- (Effective Java Item 63) Java ‐ 문자열 연결은 느리니 주의하라
- (Effective Java Item 64) Java ‐ 객체는 인터페이스를 사용해 참조하라
- (Effective Java Item 65) Java ‐ 리플렉션보다는 인터페이스를 사용하라
- (Effective Java Item 66) Java ‐ 네이티브 메서드는 신중히 사용하라
- (Effective Java Item 67) Java ‐ 최적화는 신중히 하라
- (Effective Java Item 68) Java ‐ 일반적으로 통용되는 명명 규칙을 따르라
- (Effective Java Item 69) Java ‐ 예외는 진짜 예외 상황에만 사용하라
- (Effective Java Item 70) Java ‐ 복구할 수 있는 상황에는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라
- (Effective Java Item 71) Java ‐ 필요 없는 검사 예외 사용은 피하라
- (Effective Java Item 72) Java ‐ 표준 예외를 사용하라
- (Effective Java Item 73) Java ‐ 추상화 수준에 맞는 예외를 던지라
- (Effective Java Item 74) Java ‐ 메서드가 던지는 모든 예외를 문서화하라
- (Effective Java Item 75) Java ‐ 예외의 상세 메시지에 실패 관련 정보를 담으라
- (Effective Java Item 76) Java ‐ 가능한 한 실패 원자적으로 만들라
- (Effective Java Item 77) Java ‐ 예외를 무시하지 말라
- (Effective Java Item 78) Java - 공유 중인 가변 데이터는 동기화해 사용하라
- (Effective Java Item 79) Java - 과도한 동기화는 피하라
- (Effective Java Item 80) Java - 쓰레드보다는 실행자, 태스크, 스트림을 애용하라
- (Effective Java Item 81) Java - wait와 notify는 동시성 유틸리티를 애용하라
- (Effective Java Item 82) Java - 쓰레드 안전성 수준을 문서화하라
- (Effective Java Item 83) Java - 지연 초기화는 신중히 사용하라
- (Effective Java Item 84) Java - 프로그램의 동작을 쓰레드 스케줄러에 기대지 말라
- (Effective Java Item 85) Java - 자바 직렬화의 대안을 찾으라
- (Effective Java Item 86) Java - Serializable을 구현할지는 신중히 결정하라
- (Effective Java Item 87) Java - 커스텀 직렬화 형태를 고려해보라
- (Effective Java Item 88) Java - readObject 메서드는 방어적으로 작성하라
- (Effective Java Item 89) Java - 인스턴스 수를 통제해야 한다면 readResolve보다는 열거 타입을 사용하라
- [(Effective Java Item 90) Java - 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라]
- (Effective Kotlin Item 1) Kotlin - 가변성을 제한하라
- (Effective Kotlin Item 2) Kotlin - 임계 영역을 제거하라
- (Effective Kotlin Item 3) Kotlin - 가능한 한 빨리 플랫폼 타입을 제거하라
- (Effective Kotlin Item 4) Kotlin - 변수의 스코프를 최소화하라
- (Effective Kotlin Item 5) Kotlin - 인수와 상태에 대한 기대치를 명시하라
- (Effective Kotlin Item 6) Kotlin - 사용자 정의 오류보다 표준 오류를 선호하라
- (Effective Kotlin Item 7) Kotlin - 결과가 없을 가능성이 있는 경우 널 가능 또는 Result 반환 타입을 선호하라
- (Effective Kotlin Item 8) Kotlin - use를 사용하여 리소스를 닫아라
- (Effective Kotlin Item 9) Kotlin - 단위 테스트를 작성하라
- (Effective Kotlin Item 10) Kotlin - 가독성을 목표로 설계하라
- (Effective Kotlin Item 11) Kotlin - 연산자의 의미는 함수의 이름과 일치해야 한다
- (Effective Kotlin Item 12) Kotlin - 가독성을 높이려면 연산자를 사용하라
- (Effective Kotlin Item 13) Kotlin - 타입 명시를 고려하라
- (Effective Kotlin Item 14) Kotlin - 리시버를 명시적으로 참조하라
- (Effective Kotlin Item 15) Kotlin - 프로퍼티는 동작이 아닌 상태를 나타내야 한다
- (Effective Kotlin Item 16) Kotlin - Unit?을 반환이나 연산에 사용하지 말라
- (Effective Kotlin Item 17) Kotlin - 이름 있는 인수 사용을 고려하라
- (Effective Kotlin Item 18) Kotlin - 코딩 컨벤션을 준수하라
- (Effective Kotlin Item 19) Kotlin - knowledge를 반복하지 말라
- (Effective Kotlin Item 20) Kotlin - 일반적인 알고리즘을 반복하지 말라
- (Effective Kotlin Item 21) Kotlin - 일반적인 알고리즘을 구현할 때 제네릭을 사용하라
- (Effective Kotlin Item 22) Kotlin - 타입 매개변수의 섀도잉을 피하라
- (Effective Kotlin Item 23) Kotlin - 제네릭 타입에 변성 한정자 사용을 고려하라
- (Effective Kotlin Item 24) Kotlin - 공통 모듈을 추출해서 여러 플랫폼에서 재사용하라
- (Effective Kotlin Item 25) Kotlin - 각각의 함수는 하나의 추상화 수준으로 작성하라
- (Effective Kotlin Item 26) Kotlin - 변경으로부터 코드를 보호하려면 추상화를 사용하라
- (Effective Kotlin Item 27) Kotlin - API 안정성을 명시하라
- (Effective Kotlin Item 28) Kotlin - 외부 API를 래핑하는 것을 고려하라
- (Effective Kotlin Item 29) Kotlin - 가시성을 최소화하라
- (Effective Kotlin Item 30) Kotlin - 문서로 규약을 정의하라
- (Effective Kotlin Item 31) Kotlin - 추상화 규약을 준수하라
- (Effective Kotlin Item 32) Kotlin - 보조 생성자 대신 팩토리 함수를 고려하라
- (Effective Kotlin Item 33) Kotlin - 이름 있는 선택적 인수를 갖는 기본 생성자 사용을 고려하라
- (Effective Kotlin Item 34) Kotlin - 복잡한 객체 생성을 위해 DSL 정의를 고려하라
- (Effective Kotlin Item 35) Kotlin - 의존성 주입을 고려하라
- (Effective Kotlin Item 36) Kotlin - 상속보다 합성을 선호하라
- (Effective Kotlin Item 37) Kotlin - 데이터 묶음을 표현할 때 data 한정자를 사용하라
- (Effective Kotlin Item 38) Kotlin - 연산과 행동을 전달하려면 함수 타입이나 함수형 인터페이스를 사용하라
- (Effective Kotlin Item 39) Kotlin - 제한된 계층구조를 표현하기 위해 sealed 클래스와 sealed 인터페이스를 사용하라
- [(Effective Kotlin Item 40) Kotlin - 태그 클래스 대신 클래스 계층구조를 선호하라]
- [(Effective Kotlin Item 41) Kotlin - 열거형 클래스를 사용해서 값 목록을 나타내라]
- [(Effective Kotlin Item 42) Kotlin - equals의 규약을 준수하라]
- [(Effective Kotlin Item 43) Kotlin - hashCode의 규약을 준수하라]
- [(Effective Kotlin Item 44) Kotlin - compareTo의 규약을 준수하라]
- [(Effective Kotlin Item 45) Kotlin - API의 필수적이지 않은 부분을 확장으로 추출하는 것을 고려하라]
- [(Effective Kotlin Item 46) Kotlin - 멤버 확장 함수를 피하라]
- (Effective Kotlin Item 3) Kotlin - variable
- (Effective Kotlin Item 4) Kotlin - primitive types, literals, and operations
- (Effective Kotlin Item 5) Kotlin - control Flow: if, when, try, and while
- (Effective Kotlin Item 6) Kotlin - function
- (Effective Kotlin Item 7) Kotlin - for
- (Effective Kotlin Item 8) Kotlin - Null Safety and Nullable Types
- (Effective Kotlin Item 9) Kotlin - Class
- (Effective Kotlin Item 10) Kotlin - Extend
- (Effective Kotlin Item 11) Kotlin - Data Class
- (Effective Kotlin Item 12) Kotlin - Object
- (Effective Kotlin Item 13) Kotlin ‐ Exception
- (Effective Kotlin Item 14) Kotlin - Enum Classes
- (Effective Kotlin Item 15) Kotlin - Sealed Classes and Interfaces
- (Effective Kotlin Item 16) Kotlin - Annotation Classes
- (Effective Kotlin Item 17) Kotlin - Extensions
- (Effective Kotlin Item 18) Kotlin - Collections
- (Effective Kotlin Item 19) Kotlin - Operator Overloading
- (Effective Kotlin Item 20) Kotlin - The Beauty of the Type System
- (Effective Kotlin Item 21) Kotlin - Generic
- Reactive Programming - Reactive Streams
- Reactive Programming - Blocking I/O & Non-Blocking I/O
- Reactive Programming - Reactor Outline
- Reactive Programming - Marble Diagram
- Reactive Programming - Cold Sequence & Hot Sequence
- [Reactive Programming - Backpressure]
- [Reactive Programming - Sinks]
- [Reactive Programming - Scheduler]
- [Reactive Programming - Context]
- [Reactive Programming - Debugging]
- [Reactive Programming - Testing]
- [Reactive Programming - Operators]
- [Reactive Programming - Spring Webflux]
- [Reactive Programming - Annotation Based Controller]
- [Reactive Programming - Functional Endpoint]
- [Reactive Programming - Spring Data R2DBC]
- [Reactive Programming - Exception Handling]
- [Reactive Programming - WebClient]
- [Reactive Programming - Reactive Streaming Data Processing]
- (Clean Code 2) Clean Code - 의미 있는 이름⭐
- (Clean Code 3) Clean Code - 함수
- (Clean Code 4) Clean Code - 주석
- (Clean Code 5) Clean Code - 형식 맞추기
- (Clean Code 6) Clean Code - 객체와 자료구조⭐
- (Clean Code 7) Clean Code - 오류 처리⭐
- (Clean Code 8) Clean Code - 경계⭐
- (Clean Code 9) Clean Code - 단위 테스트⭐
- (Clean Code 10) Clean Code - 클래스⭐