2022-12-17-TIL
2022-12-17-TIL
Today I Learned
상속과 합성을 비교하고 Go가 상속 없이 합성만으로 재사용을 하는 방식을 봤다. 이어서 WebFlux와 Kotlin 코루틴 환경에서 트랜잭션이 어떻게 동작하는지, EntityManager의 스레드 안전성, Feign, 배치와 스케줄러의 차이를 정리했다.
Inheritance vs Composition
상속은 “A는 B다(is-a)” 관계를 컴파일 시점에 고정하고 부모의 구현을 그대로 물려받는다. 합성은 “A는 B를 가진다(has-a)” 관계로, 다른 객체를 필드로 두고 필요한 일을 위임한다. “상속보다 합성을 선호하라”는 원칙은 Design Patterns(1994) 이후 널리 퍼졌다.
상속의 문제는 부모와 자식이 강하게 결합된다는 점이다. 부모의 내부 구현이 바뀌면 자식이 깨질 수 있고(취약한 기반 클래스 문제), 자식은 원치 않는 부모 메서드까지 공개하게 된다. 합성은 인터페이스를 통해 협력하므로 런타임에 구현을 바꿀 수 있고 캡슐화가 유지된다. 그렇다고 상속이 틀린 것은 아니다. Wikipedia 문서도 상속과 합성이 보통 함께 쓰인다고 설명하며, 진짜 is-a 관계이고 부모가 확장을 염두에 두고 설계된 경우에는 상속이 자연스럽다.
1
2
3
4
5
6
7
8
// 상속: HashSet의 내부 구현(addAll이 add를 호출하는지)에 의존하게 된다
class CountingSet<E> extends HashSet<E> { ... }
// 합성: Set 인터페이스에만 의존하고 호출을 위임한다
class CountingSet<E> implements Set<E> {
private final Set<E> delegate;
...
}
- https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose
- https://betterprogramming.pub/inheritance-vs-composition-2fa0cdd2f939
- https://tecoble.techcourse.co.kr/post/2020-05-18-inheritance-vs-composition/
- https://mangkyu.tistory.com/199#:~:text=%EC%83%81%EC%86%8D%EC%9D%80%20%EC%BB%B4%ED%8C%8C%EC%9D%BC%20%EC%8B%9C%EC%A0%90%EC%97%90,%EB%AF%80%EB%A1%9C%20%EC%84%A4%EA%B3%84%EA%B0%80%20%EC%9C%A0%EC%97%B0%ED%95%B4%EC%A7%84%EB%8B%A4.
- https://incheol-jung.gitbook.io/docs/q-and-a/architecture/undefined-2
- https://biggwang.github.io/2019/07/31/OOP/%EC%83%81%EC%86%8D%EB%B3%B4%EB%8B%A4%EB%8A%94%20%ED%95%A9%EC%84%B1%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%20%ED%95%98%EB%8A%94%20%EC%9D%B4%EC%9C%A0/
- https://www.digitalocean.com/community/tutorials/composition-vs-inheritance
- https://www.infoworld.com/article/3409071/java-challenger-7-debugging-java-inheritance.html
- https://www.adservio.fr/post/composition-vs-inheritance
- https://en.wikipedia.org/wiki/Composition_over_inheritance
- https://veerpalbrar.github.io/blog/2021/06/30/Inheritance-vs-Composition
- https://lwn.net/Articles/787800/
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
Composition in Golang
Effective Go는 Go가 타입 기반의 일반적인 서브클래싱을 제공하지 않지만, 구조체나 인터페이스 안에 타입을 임베딩해 구현을 “빌려 오는” 방법이 있다고 설명한다. 임베딩된 타입의 메서드는 바깥 타입의 메서드처럼 호출할 수 있지만, 그 메서드의 리시버는 여전히 안쪽 타입이다. 상속처럼 보이지만 실제로는 자동 위임이다.
1
2
3
4
5
6
type Logger struct{}
func (Logger) Log(msg string) { fmt.Println(msg) }
type Service struct {
Logger // 임베딩: s.Log(...)를 바로 호출할 수 있다
}
인터페이스도 io.ReadWriter가 io.Reader와 io.Writer를 임베딩하듯 작은 인터페이스를 조합해 만든다. 언어 차원에서 합성을 기본으로 강제하는 셈이다.
- https://www.geeksforgeeks.org/composition-in-golang/
- https://golangbot.com/inheritance/
- https://go.dev/doc/effective_go
OOP
- https://www.techtarget.com/searchapparchitecture/definition/object-oriented-programming-OOP
WebFlux
Spring WebFlux는 Spring 5에서 추가된 논블로킹 웹 스택이다. Servlet 기반 Spring MVC가 요청마다 스레드 하나를 점유하는 것과 달리, 적은 수의 이벤트 루프 스레드로 많은 요청을 처리하고 Reactor의 Mono, Flux로 결과를 표현한다. Spring 블로그에 따르면 Spring Framework 5.2부터 Kotlin 코루틴을 써서 리액티브 스택을 명령형에 가까운 코드로 작성할 수 있다. suspend 함수가 Mono를, Flow가 Flux를 대신한다.
- https://heeyeah.github.io/spring/2020-02-29-web-flux/
- https://spring.io/blog/2019/04/12/going-reactive-with-spring-coroutines-and-kotlin-flow
Coroutine and Transaction Manager
기존 Spring 트랜잭션은 트랜잭션 상태와 커넥션을 ThreadLocal에 묶는다. JpaTransactionManager도 EntityManager를 현재 스레드에 바인딩한다. 그런데 코루틴은 중단(suspend) 후 다른 스레드에서 재개될 수 있으므로, 스레드에 묶인 트랜잭션 정보가 따라가지 않는다. 이슈 #22915가 바로 이 문제를 제기했고 Spring Framework 5.2에서 코루틴 트랜잭션 지원이 들어갔다.
핵심은 이 지원이 리액티브 트랜잭션(ReactiveTransactionManager) 위에서 동작한다는 점이다. Spring 블로그의 리액티브 트랜잭션 글은 R2DBC와 MongoDB를 첫 구현으로 소개하고, 선언적 @Transactional과 프로그래밍 방식의 TransactionalOperator를 제공한다. JPA/JDBC처럼 블로킹 드라이버를 쓰는 코드라면, 링크된 Stack Overflow 답변들처럼 트랜잭션 경계 전체를 하나의 블로킹 호출(withContext(Dispatchers.IO) 안의 일반 @Transactional 메서드 등)로 묶어 중간에 스레드가 바뀌지 않게 하는 방식으로 다룬다.
- https://github.com/spring-projects/spring-framework/issues/22915
- https://stackoverflow.com/questions/64136645/is-it-possible-to-use-transactional-and-kotlin-coroutines-in-spring-boot
- https://codeinlife.tistory.com/73
- https://github.com/spring-projects/spring-framework/issues/26705
- https://stackoverflow.com/questions/72453075/is-there-a-kotlin-coroutine-friendly-way-to-do-transactions-with-jooq
- https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html
- https://ohadshai.medium.com/just-a-small-example-of-how-kotlin-coroutines-are-great-c9774fe8434
- https://www.educative.io/answers/kotlin-coroutines-make-it-make-sense
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/jpa/JpaTransactionManager.html
- https://baekjungho.github.io/wiki/spring/spring-transactionmanager/
- https://medium.com/digitalfrontiers/reactive-or-coroutines-between-a-rock-and-a-hard-place-6a41a151195a
- https://foojay.io/today/build-and-test-non-blocking-web-applications-with-spring-webflux-kotlin-and-coroutines/
- https://spring.io/blog/2019/05/16/reactive-transactions-with-spring
- https://github.com/spring-projects/spring-framework/issues/22915
Coroutine vs Goroutine
이름은 비슷하지만 스케줄링 방식이 다르다. Kotlin 코루틴은 suspend 지점에서 스스로 실행을 양보하는 협력적 방식이고, 어느 스레드에서 실행될지는 디스패처가 정한다. 고루틴은 Go 런타임이 관리하는 경량 실행 단위로, Effective Go는 고루틴이 여러 OS 스레드에 다중화되어 하나가 I/O로 블록되어도 다른 고루틴은 계속 실행된다고 설명한다. 개발자는 양보 지점을 신경 쓰지 않고 블로킹 코드처럼 작성하고, 통신은 채널로 한다.
- https://stackoverflow.com/questions/18058164/is-a-go-goroutine-a-coroutine
- http://www.golangpatterns.info/concurrency/coroutines
- https://www.quora.com/Are-Python-coroutines-and-Go-goroutines-the-same
- https://medium.com/@asadawadia/parallel-requests-in-kotlin-vs-golang-72fc8a8fead7
- https://news.ycombinator.com/item?id=18172207
R2DBC
R2DBC는 관계형 DB에 논블로킹으로 접근하기 위한 리액티브 드라이버 명세다. JDBC가 블로킹 API라서 WebFlux 환경에서는 이벤트 루프를 막게 되는 문제를 해결한다. Spring Data R2DBC는 JPA 같은 ORM이 아니라 지연 로딩이나 영속성 컨텍스트가 없는 단순한 매핑 계층이며, 트랜잭션은 R2dbcTransactionManager와 @Transactional 또는 TransactionalOperator로 다룬다.
- https://www.vinsguru.com/spring-data-r2dbc-transaction/
- https://steady-coding.tistory.com/631
- https://medium.com/geekculture/spring-data-r2dbc-transactions-cd5e064d59a8
- https://dzone.com/articles/you-dont-need-hibernate-with-spring-webflux-and-r2
Is EntityManager Thread-Safe?
EntityManager 자체는 스레드 안전하지 않다. 그런데 Spring 빈에 @PersistenceContext로 주입한 EntityManager는 여러 스레드에서 써도 된다. 실제로 주입되는 것은 공유 프록시이고, 호출될 때마다 현재 스레드의 트랜잭션에 바인딩된 실제 EntityManager를 찾아 위임하기 때문이다. JpaTransactionManager 문서도 SharedEntityManagerCreator와 @PersistenceContext가 스레드에 바인딩된 EntityManager를 인식해 트랜잭션에 참여한다고 설명한다. 같은 이유로 EntityManagerFactory에서 직접 만든 EntityManager를 공유하면 안 된다.
- https://stackoverflow.com/questions/24643863/is-entitymanager-really-thread-safe
- https://www.inflearn.com/questions/280483/entitymanager-thread-safe%EA%B4%80%EB%A0%A8-%EB%AC%B8%EC%9D%98%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4
- https://medium.com/@SlackBeck/spring-container%EB%8A%94-jpa-entitymanager%EC%9D%98-thread-safety%EB%A5%BC-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%B3%B4%EC%9E%A5%ED%95%A0%EA%B9%8C-1650473eeb64
Feign
Spring Cloud OpenFeign 문서는 Feign을 선언적 웹 서비스 클라이언트로 소개한다. 인터페이스를 만들고 @FeignClient와 Spring MVC 스타일 애너테이션(@GetMapping 등)을 붙이면 구현체가 자동으로 만들어진다. RestTemplate으로 URL과 파라미터를 조립하던 코드를 인터페이스 선언으로 대체할 수 있어서, 마이크로서비스 사이 호출이 많을 때 특히 편하다.
- https://techblog.woowahan.com/2630/
- https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html
- https://vmpo.tistory.com/109
Spring Batch vs Scheduler
둘은 경쟁 관계가 아니라 역할이 다르다. 스케줄러(Spring @Scheduled, Quartz)는 “언제 실행할지”를 정하고, Spring Batch는 “대량 데이터를 어떻게 안정적으로 처리할지”를 담당한다. Spring Batch는 청크 단위 처리, 실패 시 재시작, 실행 이력 메타데이터 같은 기능을 제공하지만 스스로 실행 시점을 정하지 않으므로 스케줄러나 Jenkins 같은 외부 도구가 Job을 실행한다.
- https://smujihoon.tistory.com/226
- https://yongku.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%EC%B9%98Spring-Batch%EC%99%80-Quartz
Reactive Programming
리액티브 프로그래밍은 데이터 흐름과 변경 전파를 중심으로 비동기 코드를 작성하는 방식이다. JVM에서는 Reactive Streams 명세가 기준이 된다. 이 명세는 논블로킹 배압(back pressure)을 가진 비동기 스트림 처리 표준을 목표로 하며, Publisher, Subscriber, Subscription, Processor 네 인터페이스를 정의한다. 배압은 소비자가 처리할 수 있는 만큼만 요청해서 생산자가 소비자를 압도하지 않게 하는 장치다. JDK 9의 java.util.concurrent.Flow가 같은 의미의 인터페이스를 제공하고, Reactor와 RxJava가 이 명세를 구현한다.
- https://juneyr.dev/reactive-programming
댓글
아직 댓글이 없습니다