Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Kopring으로 효율적인 백엔드 구성하기
Search
byeongsu
May 13, 2023
Programming
0
270
Kopring으로 효율적인 백엔드 구성하기
KotlinConf 2023 Global in Songdo에서 Java+Spring에서 Kotiln 으로 언어 변경 시 얻을 수 있는 이점을 소개한 자료입니다.
byeongsu
May 13, 2023
Tweet
Share
Other Decks in Programming
See All in Programming
タスクの特性や不確実性に応じた最適な作業スタイルの選択(ペアプロ・モブプロ・ソロプロ)と実践 / Optimal Work Style Selection: Pair, Mob, or Solo Programming.
honyanya
3
160
Domain-centric? Why Hexagonal, Onion, and Clean Architecture Are Answers to the Wrong Question
olivergierke
2
840
Range on Rails ―「多重範囲型」という新たな選択肢が、複雑ロジックを劇的にシンプルにしたワケ
rizap_tech
0
120
CI_CD「健康診断」のススメ。現場でのボトルネック特定から、健康診断を通じた組織的な改善手法
teamlab
PRO
0
210
私達はmodernize packageに夢を見るか feat. go/analysis, go/ast / Go Conference 2025
kaorumuta
2
550
[Kaigi on Rais 2025] 全問正解率3%: RubyKaigiで出題したやりがちな危険コード5選
power3812
0
130
Introducing ReActionView: A new ActionView-Compatible ERB Engine @ Kaigi on Rails 2025, Tokyo, Japan
marcoroth
3
1k
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
290
GraphQL×Railsアプリのデータベース負荷分散 - 月間3,000万人利用サービスを無停止で
koxya
1
1.3k
After go func(): Goroutines Through a Beginner’s Eye
97vaibhav
0
380
TFLintカスタムプラグインで始める Terraformコード品質管理
bells17
2
160
(Extension DC 2025) Actor境界を越える技術
teamhimeh
1
250
Featured
See All Featured
How STYLIGHT went responsive
nonsquared
100
5.8k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
46
7.7k
Building Better People: How to give real-time feedback that sticks.
wjessup
369
20k
Visualization
eitanlees
149
16k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
7
900
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
189
55k
Imperfection Machines: The Place of Print at Facebook
scottboms
269
13k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
23
1.5k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
30
2.7k
Testing 201, or: Great Expectations
jmmastey
45
7.7k
Java REST API Framework Comparison - PWX 2021
mraible
33
8.9k
Transcript
김병수 KotlinConf’23 Global in Songdo GDG Songdo X Incheon Kopring으로
효율적인 백엔드 구성하기
Autocrypt - Backend Engineer - Data Engineer - Kotlin +
Spring - 정의에 대한 고민 - 코드의 아름다움
Java + Spring Boot => Kotlin + Spring Boot 코드
개선 여정
서비스 요구사항 - 기사의 주행 데이터 수집 - 1초당 위치
정보 수집 - 1분당 주소 정보 저장 - 주소 정보 조회 시 1sec 소요 - 일반적으로 주행은 약 20분 소요
Java + Spring Code
Kotlin으로 언어를 변경한다면 어떤 장점이 있을까요?
1. 가독성 향상 2. 유용한 Extension function 3. 간단한 비동기
처리
다크모드 전환
Entity 설계
@Entity @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class Driver { @Id
@Column private String id; @Column private String name; @Column private String mobileNumber; @Column private LocalDateTime createDate; } Entity 설계
@Entity class Driver ( @Id @Column val id: String =
StringUtil.uuid(), @Column var name: String = "", @Column var mobileNumber: String? = null, @Column val createDate: LocalDateTime = LocalDateTime.now(), ) @Entity @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class Driver { @Id @Column private String id; @Column private String name; @Column private String mobileNumber; @Column private LocalDateTime createDate; } Entity 설계 Java Kotlin
@Entity class Driver ( @Id @Column val id: String =
StringUtil.uuid(), @Column var name: String = "", @Column var mobileNumber: String? = null, @Column val createDate: LocalDateTime = LocalDateTime.now(), ) @Entity @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class Driver { @Id @Column private String id; @Column private String name; @Column private String mobileNumber; @Column private LocalDateTime createDate; } Entity 설계 Java Kotlin
Entity 설계 @Entity class Driver ( @Id @Column val id:
String = StringUtil.uuid(), @Column var name: String = "", @Column var mobileNumber: String? = null, @Column val createDate: LocalDateTime = LocalDateTime.now(), ) @Entity @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class Driver { @Id @Column private String id; @Column private String name; @Column private String mobileNumber; @Column private LocalDateTime createDate; } Java Kotlin
@Entity class Driver ( @Id @Column val id: String =
StringUtil.uuid(), @Column var name: String = "", @Column var mobileNumber: String? = null, @Column val createDate: LocalDateTime = LocalDateTime.now(), ) @Entity @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class Driver { @Id @Column private String id; @Column private String name; @Column private String mobileNumber; @Column private LocalDateTime createDate; } Entity 설계 Java Kotlin
val driver = Driver( id = StringUtil.uuid(), name = "",
mobileNumber = null, createDate = LocalDateTime.now() ) Driver driver = new Driver( StringUtil.uuid(), "", null, LocalDateTime.now() ); Java Kotlin Entity 설계
val driver = Driver( id = StringUtil.uuid(), name = "",
mobileNumber = null, createDate = LocalDateTime.now() ) Driver driver = new Driver( StringUtil.uuid(), "", null, LocalDateTime.now() ); Java Kotlin Entity 설계
Entity 설계 class Driving ( val id: String = StringUtil.uuid(),
var startTime: LocalDateTime = LocalDateTime.now(), var endTime: LocalDateTime? = null, var driver: Driver? = null, var vehicle: Vehicle? = null, var departureAddress: String = "", var destinationAddress: String? = null, // ... // ... var customerName: String = "", var customerMobileNumber: String? = null, var customerEmailAddress: String? = null, var drivingDistance: Double? = null, val createDate: LocalDateTime = LocalDateTime.now(), var updateDate: LocalDateTime = LocalDateTime.now(), )
val driving = Driving( startTime = startTime, endTime = endTime,
customerName = customerName, departureAddress = departureAddress, destinationAddress = destinationAddress, ) Driving driving = new Driving( StringUtil.uuid(), startTime, endTime, null, null, departureAddress, destinationAddress, // ... customerName, null, null, null, LocalDateTime.now(), LocalDateTime.now() ) Java Kotlin Entity 설계
val driving = Driving( startTime = startTime, endTime = endTime,
customerName = customerName, departureAddress = departureAddress, destinationAddress = destinationAddress, ) Driving driving = new Driving( StringUtil.uuid(), startTime, endTime, null, null, departureAddress, destinationAddress, // ... customerName, null, null, null, LocalDateTime.now(), LocalDateTime.now() ) Java Kotlin Entity 설계 ???
val driving = Driving( startTime = startTime, endTime = endTime,
customerName = customerName, departureAddress = departureAddress, destinationAddress = destinationAddress, ) Driving driving = new Driving( StringUtil.uuid(), startTime, endTime, null, null, departureAddress, destinationAddress, // ... customerName, null, null, null, LocalDateTime.now(), LocalDateTime.now() ) Java Kotlin Entity 설계
1. Getter & Setter 제거 a. 주의: Lombok 사용 불가
2. Named Parameter 3. Default Constructor Entity 설계 => 가독성 향상
참고 Kotlin 전환 시 Lombok 에 대하여
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.jetbrains.kotlin:kotlin-reflect") // mariadb implementation
("org.mariadb.jdbc:mariadb-java-client") // encoding decoding codec implementation ("commons-codec:commons-codec:1.15") // coroutine implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") testImplementation("org.springframework.boot:spring-boot-starter-test") } 적용한 dependencies
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.springframework.boot:spring-boot-starter-data-jdbc") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.jetbrains.kotlin:kotlin-reflect") // mariadb implementation
("org.mariadb.jdbc:mariadb-java-client") // encoding decoding codec implementation ("commons-codec:commons-codec:1.15") // coroutine implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") testImplementation("org.springframework.boot:spring-boot-starter-test") } 적용한 dependencies No Lombok!
주의할 점 Kotlin Java … Annotation Processing … .class .class
1 2 No Code, Lombok Processed
주의할 점
Business Logic 구현
Driving - foo - drivingPath - bar Location - drivingId
- ord - lat - lng - address? Business Logic 구현
fun doneDriving( drivingId: String ): DoneDrivingRes { val driving =
drivingRepository .findFirstById( drivingId ) ?: return DoneDrivingRes(null) val path = locationService .getLocationListByDrivingId( drivingId = driving.id ) driving.setDrivingDistance(path) /* other business logic */ /* other business logic */ drivingRepository.save(driving) locationService.updateAddress(path) return DoneDrivingRes( buildDrivingDto(driving) ) } Business Logic 구현
fun doneDriving( drivingId: String ): DoneDrivingRes { val driving =
drivingRepository .findFirstById( drivingId ) ?: return DoneDrivingRes(null) val path = locationService .getLocationListByDrivingId( drivingId = driving.id ) driving.setDrivingDistance(path) /* other business logic */ /* other business logic */ drivingRepository.save(driving) locationService.updateAddress(path) return DoneDrivingRes( buildDrivingDto(driving) ) } Business Logic 구현
Location - drivingId - ord - lat - lng -
address? Business Logic 구현 drivingId ord lat lng address testId 0 37.387540 126.638847 null testId 1 37.386466 126.639485 null … … … … … testId 59 37.392561 126.635330 null testId 60 37.393679 126.634572 null testId 61 37.395486 126.633526 null … … … … …
Location - drivingId - ord - lat - lng -
address? Business Logic 구현 drivingId ord lat lng address testId 0 37.387540 126.638847 null testId 1 37.386466 126.639485 null … … … … … testId 59 37.392561 126.635330 null testId 60 37.393679 126.634572 null testId 61 37.395486 126.633526 null … … … … …
Location - drivingId - ord - lat - lng -
address? Business Logic 구현 drivingId ord lat lng address testId 0 37.387540 126.638847 인천 연수구 … testId 1 37.386466 126.639485 null … … … … … testId 59 37.392561 126.635330 null testId 60 37.393679 126.634572 인천 연수구 … testId 61 37.395486 126.633526 null … … … … …
private void updateAddress( List<Location> path ) throws InterruptedException { for(int
i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } Business Logic 구현
private void updateAddress( List<Location> path ) throws InterruptedException { for(int
i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } Business Logic 구현
private String findAddress( Double lat, Double lng ) throws InterruptedException
{ Thread.sleep(1000); return "address"; } Business Logic 구현
private void updateAddress( List<Location> path ) throws InterruptedException { for(int
i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } Business Logic 구현
fun updateAddress( path: List<Location> ) { path .chunked(60) .map {
chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 Java Kotlin private void updateAddress( List<Location> path ) throws InterruptedException { for(int i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } Business Logic 구현
fun updateAddress( path: List<Location> ) { path .chunked(60) .map {
chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 Java Kotlin private void updateAddress( List<Location> path ) throws InterruptedException { for(int i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } Business Logic 구현
fun updateAddress( path: List<Location> ) { path .chunked(60) .map {
chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 Java Kotlin private void updateAddress( List<Location> path ) throws InterruptedException { for(int i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } 다시, 주행 종료 API로 돌아가봅시다 Business Logic 구현
fun doneDriving( drivingId: String ): DoneDrivingRes { val driving =
drivingRepository .findFirstById( drivingId ) ?: return DoneDrivingRes(null) val path = locationService .getLocationListByDrivingId( drivingId = driving.id ) driving.setDrivingDistance(path) /* other business logic */ /* other business logic */ drivingRepository.save(driving) locationService.updateAddress(path) return DoneDrivingRes( buildDrivingDto(driving) ) } Business Logic 구현
fun doneDriving( drivingId: String ): DoneDrivingRes { val driving =
drivingRepository .findFirstById( drivingId ) ?: return DoneDrivingRes(null) val path = locationService .getLocationListByDrivingId( drivingId = driving.id ) driving.setDrivingDistance(path) /* other business logic */ /* other business logic */ drivingRepository.save(driving) locationService.updateAddress(path) return DoneDrivingRes( buildDrivingDto(driving) ) } Business Logic 구현
Location - drivingId - ord - lat - lng -
address? Business Logic 구현 drivingId ord lat lng address testId 0 37.387540 126.638847 null testId 1 37.386466 126.639485 null … … … … … testId 59 37.392561 126.635330 null testId 60 37.393679 126.634572 null testId 61 37.395486 126.633526 null … … … … …
Business Logic 구현 drivingId ord lat lng address testId 0
37.387540 126.638847 null testId 1 37.386466 126.639485 null … … … … … testId 59 37.392561 126.635330 null testId 60 37.393679 126.634572 null testId 61 37.395486 126.633526 null … … … … … +Distance +Distance +Distance Driving Distance
driving .setDrivingDistance( getDistanceOfPath( path ) ); driving.setDrivingDistance(path) // 후략 }
fun Driving.setDrivingDistance( path: List<Location> ) { this.drivingDistance = locationService .getDistanceOfPath(path) } Java Kotlin Business Logic 구현
driving.setDrivingDistance(path) // 후략 } fun Driving.setDrivingDistance( path: List<Location> ) {
this.drivingDistance = locationService .getDistanceOfPath(path) } driving .setDrivingDistance( getDistanceOfPath( path ) ); Java Kotlin Business Logic 구현
driving .setDrivingDistance( getDistanceOfPath( path ) ); Java Kotlin driving.setDrivingDistance(path) //
후략 } fun Driving.setDrivingDistance( path: List<Location> ) { this.drivingDistance = locationService .getDistanceOfPath(path) } Business Logic 구현
public Double getDistanceOfPath( List<Location> path ) { double distance =
0.0; for(int i = 1; i < path.size(); i++) { distance += GeoUtil .calculateDistance( path.get(i-1), path.get(i) ); } return distance; } fun getDistanceOfPath( locationList: List<Location> ): Double = locationList .calculateDistanceOfPath() Java Kotlin Business Logic 구현
public Double getDistanceOfPath( List<Location> path ) { double distance =
0.0; for(int i = 1; i < path.size(); i++) { distance += GeoUtil .calculateDistance( path.get(i-1), path.get(i) ); } return distance; } Java Kotlin fun getDistanceOfPath( locationList: List<Location> ): Double = locationList .calculateDistanceOfPath() Business Logic 구현
fun List<Location> .calculateDistanceOfPath(): Double = List(this.size) { index -> if(index
< 1) return 0.0 GeoUtil.calculateDistance( this[index - 1], this[index] ) } .sum() fun getDistanceOfPath( locationList: List<Location> ): Double = locationList .calculateDistanceOfPath() Business Logic 구현
• forEach / map / let • custom Extensions =>
유용한 Extension Functions Business Logic 구현
성능 개선
fun updateAddress( path: List<Location> ) { path .chunked(60) .map {
chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 Java Kotlin private void updateAddress( List<Location> path ) throws InterruptedException { for(int i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } updateAddress 기능을 살펴봅시다 성능 개선
fun updateAddress( path: List<Location> ) { path .chunked(60) .map {
chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 Java Kotlin private void updateAddress( List<Location> path ) throws InterruptedException { for(int i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } 성능 개선
fun updateAddress( path: List<Location> ) { path .chunked(6) .map {
chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 Java Kotlin private void updateAddress( List<Location> path ) throws InterruptedException { for(int i=0; i<path.size(); i+=6) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } 약 20분 주행, 1초에 한 번 위치 수집, 1분에 한 번 주소 저장 => 1200개 위치 데이터, 20s 소요 성능 개선
fun updateAddress( path: List<Location> ) { path .chunked(6) .map {
chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 Java Kotlin private void updateAddress( List<Location> path ) throws InterruptedException { for(int i=0; i<path.size(); i+=6) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } 약 20분 주행, 1초에 한 번 위치 수집, 5초에 한 번 주소 저장 => 1200개 위치 데이터, 240s 소요 성능 개선
fun updateAddress( path: List<Location> ) { path .chunked(60) .map {
chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 Java Kotlin private void updateAddress( List<Location> path ) throws InterruptedException { for(int i=0; i<path.size(); i+=60) { Location location = path.get(i); location.setAddress( findAddress( location.getLat(), location.getLng() ) ); } } 성능 개선
fun updateAddressAsync( path: List<Location> ) { CoroutineScope(Dispatchers.IO).launch { val deferredPath
= path .chunked(60) .map { locationChunk -> async { val top = locationChunk .first() top.address = findAddress( lat = top.lat, lng = top.lng ) top } } val addressedPath = deferredPath.awaitAll() fun updateAddress( path: List<Location> ) { path .chunked(60) .map { chunk -> val location = chunk .first() location.address = findAddress( lat = location.lat, lng = location.lng, ) location } // 후략 성능 개선
• Coroutine 성능 개선 => 간단한 비동기 처리
다크모드 종료
정리
1. 가독성 향상 2. 유용한 Extension function 3. 간단한 비동기
처리
Come with me, Use Kopring
QnA & EOF