Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kopring으로 효율적인 백엔드 구성하기

Kopring으로 효율적인 백엔드 구성하기

KotlinConf 2023 Global in Songdo에서 Java+Spring에서 Kotiln 으로 언어 변경 시 얻을 수 있는 이점을 소개한 자료입니다.

byeongsu

May 13, 2023
Tweet

Other Decks in Programming

Transcript

  1. Autocrypt - Backend Engineer - Data Engineer - Kotlin +

    Spring - 정의에 대한 고민 - 코드의 아름다움
  2. 서비스 요구사항 - 기사의 주행 데이터 수집 - 1초당 위치

    정보 수집 - 1분당 주소 정보 저장 - 주소 정보 조회 시 1sec 소요 - 일반적으로 주행은 약 20분 소요
  3. @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 설계
  4. @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
  5. @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
  6. 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
  7. @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
  8. val driver = Driver( id = StringUtil.uuid(), name = "",

    mobileNumber = null, createDate = LocalDateTime.now() ) Driver driver = new Driver( StringUtil.uuid(), "", null, LocalDateTime.now() ); Java Kotlin Entity 설계
  9. val driver = Driver( id = StringUtil.uuid(), name = "",

    mobileNumber = null, createDate = LocalDateTime.now() ) Driver driver = new Driver( StringUtil.uuid(), "", null, LocalDateTime.now() ); Java Kotlin Entity 설계
  10. 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(), )
  11. 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 설계
  12. 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 설계 ???
  13. 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 설계
  14. 1. Getter & Setter 제거 a. 주의: Lombok 사용 불가

    2. Named Parameter 3. Default Constructor Entity 설계 => 가독성 향상
  15. 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
  16. 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!
  17. Driving - foo - drivingPath - bar Location - drivingId

    - ord - lat - lng - address? Business Logic 구현
  18. 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 구현
  19. 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 구현
  20. 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 … … … … …
  21. 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 … … … … …
  22. 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 … … … … …
  23. 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 구현
  24. 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 구현
  25. private String findAddress( Double lat, Double lng ) throws InterruptedException

    { Thread.sleep(1000); return "address"; } Business Logic 구현
  26. 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 구현
  27. 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 구현
  28. 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 구현
  29. 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 구현
  30. 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 구현
  31. 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 구현
  32. 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 … … … … …
  33. 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
  34. driving .setDrivingDistance( getDistanceOfPath( path ) ); driving.setDrivingDistance(path) // 후략 }

    fun Driving.setDrivingDistance( path: List<Location> ) { this.drivingDistance = locationService .getDistanceOfPath(path) } Java Kotlin Business Logic 구현
  35. driving.setDrivingDistance(path) // 후략 } fun Driving.setDrivingDistance( path: List<Location> ) {

    this.drivingDistance = locationService .getDistanceOfPath(path) } driving .setDrivingDistance( getDistanceOfPath( path ) ); Java Kotlin Business Logic 구현
  36. driving .setDrivingDistance( getDistanceOfPath( path ) ); Java Kotlin driving.setDrivingDistance(path) //

    후략 } fun Driving.setDrivingDistance( path: List<Location> ) { this.drivingDistance = locationService .getDistanceOfPath(path) } Business Logic 구현
  37. 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 구현
  38. 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 구현
  39. 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 구현
  40. • forEach / map / let • custom Extensions =>

    유용한 Extension Functions Business Logic 구현
  41. 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 기능을 살펴봅시다 성능 개선
  42. 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() ) ); } } 성능 개선
  43. 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 소요 성능 개선
  44. 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 소요 성능 개선
  45. 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() ) ); } } 성능 개선
  46. 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 } // 후략 성능 개선