Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

64

Click here to load reader

Transcript of Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Page 1: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala, Spring-Boot, JPA 의

불편하면서도 즐거운 동거

Page 2: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala, Spring-Boot, JPA 가 동거를 시작한 이유는 ?

JPA 가 동거에 기여하는 점

Spring-Boot 가 동거에 기여하는 점

Scala 가 동거에 기여하는 점

Scala, Spring-Boot, JPA 동거할 때 고려할 사항

Page 3: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala, Spring-Boot, JPA 가 동거를 시작한 이유는 ?

Page 4: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

JPA• 단순 , 반복적인 CRUD 작업하기 싫다 .• 스키마 변경에 따른 비용을 최소화하고 싶다 .• 핵심 비즈니스 로직에 집중하고 싶다 .

Page 5: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Spring Boot• 단순 , 반복적인 Spring 설정 작업하기 싫다 .• 핵심 비즈니스 로직에 집중하고 싶다 .

Page 6: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala• 10 년 이상 Java 만 했더니 재미없더라 .• 2 년 정도 함수형 언어로 Scheme, Clojure 도전했다가 중도 포기

• 어딘가 직접적으로 활용하고 있지 않으면 동기부여가 잘 되지 않는

성격

Page 7: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

• 단순 , 반복적인 작업 제거

• 핵심 비즈니스 로직에 집중

• Java 에 싫증이나 Scala 를 선택

Page 8: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

• 단순 , 반복적인 작업 제거

• 핵심 비즈니스 로직에 집중

• Java 에 싫증이나 Scala 를 선택

Page 9: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거
Page 10: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Play 가 아닌 Scala + Spring + JPA 조합을

선택한 이유

• Spring 과 JPA 경험을 버리는 것의 아쉬움 .• 중도 포기하지 않으려면 변화를 Scala 하나로 한정 .• 변화를 싫어하는 다른 Java 개발자를 설득하기 위해 최소한의 변화 .

Page 11: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

JPA 가 동거에 기여하는 점

Page 12: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

JPA• 단순 , 반복적인 CRUD 작업하기 싫다 .• 스키마 변경에 따른 비용을 최소화하고 싶다 .• 핵심 비즈니스 로직에 집중하고 싶다 .

Page 13: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

JPA 도입에 따른 더 큰 효과요구사항이 자주 변경되는 프로젝트 초반 , 빠른 사이클로 설계에

대해 다양한 실험이 가능하다 .

Page 14: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

JPA 를 사용하지 않는 경우 – 테이블 최초 생성시

데이터베이스에 스키마 적용

비즈니스 로직 구현 테이블 설계

테이블 스키마 생성SQL 쿼리 구현

자바 테이블과 매핑되는 객체 추가

1

2

34

5

6

Page 15: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

JPA 를 사용하지 않는 경우 – 테이블 스키마 변경시

데이터베이스에 스키마 적용

비즈니스 로직 변경 ( 선택 )테이블 설계

테이블 스키마 변경칼럼 변경에 따른 SQL 쿼리 수정

자바 객체에 필드 변경

Page 16: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

비즈니스 로직 구현 객체 설계

JPA 를 사용하는 경우 – 객체 (Entity) 최초 생성시

객체 추가 및 매핑

데이터베이스에 스키마 적용

비즈니스 로직 구현 테이블 설계

테이블 스키마 생성SQL 쿼리 구현

자바 테이블과 매핑되는 객체 추가

1

2

3

Page 17: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

비즈니스 로직 구현 (선택 )

객체 설계

JPA 를 사용하는 경우 – 객체 (Entity) 필드 변경

객체 필드 변경

데이터베이스에 스키마 적용

비즈니스 로직 변경 ( 선택 ) 테이블 설계

테이블 스키마 변경칼럼 변경에 따른 SQL 쿼리 수정

자바 객체에 필드 변경

Page 18: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

JPA 도입 효과• 요구사항이 자주 변경되는 프로젝트 초반 빠른 구현 – 피드백

사이클

• 빠른 피드백 사이클은 삽질할 수 있는 시간을 확보함으로써 빠른 지식

축적이 가능하다 .• 지식 축적은 도메인에 최적화된 설계를 할 수 있도록 한다 .• 좋은 설계는 사용자의 요구사항 변화에 빠르게 대응할 수 있다 .• 개발자는 소스 코드에 대한 자부심과 여유 시간을 확보할 수 있

다 .

Page 20: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Spring-Boot 가 동거에 기여하는 점

Page 21: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Spring Boot• 단순 , 반복적인 Spring 설정 작업하기 싫다 .• 핵심 비즈니스 로직에 집중하고 싶다 .

Page 22: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

1. @IntegrationTest• Spring Boot 는 Embedded Tomcat 기반으로 동작 .• @IntegrationTest 기반으로 테스트하면 Embedded Tomcat 을

자동 실행한 후 Client 테스트가 가능 .• 특히 RestTemplate 을 활용해 API 테스트할 때 유용 .

Page 23: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

지금까지 통합 테스트 방법

WAS 시작

통합 테스트 실행

Page 24: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

@IntegrationTest 기반 API 테스트 방법

Only 통합 테스트 실행= WAS 시작 + 통합 테스트 실행

Page 25: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

@SpringApplicationConfiguration(classes = Array(classOf[MyWebConfig]))@WebAppConfiguration@IntegrationTest(Array("server.port:0"))abstract class ServerIntegrationTest { @Value("${local.server.port}") var port: Int = _ // 사용하지 않는 임의의 port 가 할당됨

val baseUrl = "http://localhost:" + port}

Page 26: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

@RunWith(classOf[SpringJUnit4ClassRunner])class UserControllerTest extends ServerIntegrationTest { val restTemplate: RestTemplate = new RestTemplate()

@Test def canCreateUser() { val result = restTemplate.postForEntity( baseUrl + "/users", postParameter(List( "email" -> "[email protected]", "password" -> "password" )), classOf[String]) assert(result.getStatusCode == HttpStatus.OK) }}

Page 27: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

2. Spring Boot Actuator• 서비스 운영시 모니터링 요소에 대한 기본 정보 제공

Page 28: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

제공하는 정보• Spring 관련 정보

• /autoconfig – Spring auto configuration 정보

• /beans - Spring Bean 목록

• /configprops - @ConfigurationProperties 정보

• /mappings - @RequestMapping 정보

• 시스템 운영 및 성능

• /dump – thread dump• /health – 애플리케이션의 정상 동작 유무

• /metrics – 애플리케이션의 메모리 상태 , heap, thread 등과 관련한 정보 제공

• http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#production-ready 에서 추가 정보 확인

Page 29: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Application Detail

출처 : https://github.com/codecentric/spring-boot-admin

Page 30: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Thread 상태

출처 : https://github.com/codecentric/spring-boot-admin

Page 31: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Actuator Remote Shell

출처 : http://nomimic.tistory.com/5

Page 32: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

빠르게 최소한의 모니터링 및 운영 환경을 구축할 수 있다 .

Page 33: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala 가 동거에 기여하는 점

Page 34: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Spring Boot + JPA 환경 조합만으로 충분히 생산성 높다 .

Page 35: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala• 10 년 이상 Java 만 했더니 재미없더라 .• 2 년 정도 함수형 언어로 Scheme, Clojure 도전했다가 중도 포기

• 어딘가 직접적으로 활용하고 있지 않으면 동기부여가 잘 되지 않는

성격

Page 36: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

1. Domain 과 DTO 의 명확한 분리에 대한 거부감이

줄어듦

• 현재 개발 추세는 Domain 객체와 DTO 에 중복되는 부분이 많아 자바

객체 하나가 Domain 역할 , DTO 역할을 하는 방식으로 구현 .• Scala 를 활용하면 각 역할별로 구현하는 것에 대한 거부감이 줄어듦

Page 37: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

@Entityclass User(pEmail: String, pNickName: String, pPassword: String) extends DomainModel { @Id @GeneratedValue var id: Long = _

@Column(unique = true, nullable = false) val email = pEmail

@Column(name = "nick_name", nullable = false) val nickName = pNickName

@Column(nullable = false) val password = pPassword

def isGuest(): Boolean = { false }}

User Entity• 반드시 setter/getter 를 생성하지 않아도

된다 .

Page 38: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

@JsonIgnoreProperties(ignoreUnknown = true)@JsonPropertyOrder(alphabetic = true)@JsonInclude(Include.NON_NULL)@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)trait View

case class UserView(id: Long, email: String, nickName: String) extends View { def this(u: User) = this(u.id, u.email, u.nickName)

def this() = this(new User())}

User View DTO• Scala case class 를 활용하면 자동으로 field 추가함 .

Page 39: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

class UserForm { @BeanProperty @Email var email: String = _

@BeanProperty @NotNull @Size(min = 3, max = 10) var nickName: String = _

@BeanProperty @NotNull @Size(min = 8, max = 15) var password: String = _

def toUser() = new User(email, nickName, password)}

User Form DTO• @BeanProperty 활용하면 setter/getter method 자동

추가

Page 40: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Domain 과 DTO 의 명확한 분리에 대한 거부감이

줄어듦

• 분리하는 것이 항상 좋은 것은 아니다 .• 상황에 따라 Entity 와 DTO 를 분리 / 통합할 것인지에

대한 역량을 키우는 것이 더 중요하다 .

Page 41: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

2. Test Fixture(Test Data) 생성하기 용이함 .• 자바에서 Test Fixture 를 생성하고 변경하기 어려움은 Test 코드를

만드는데 약간의 장애물이다 .• Scala 는 named parameter 를 통해 해결 가능

Page 42: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

email, nickname, password 를 가지는 User 객체에 대한 테스트를 데이터를 만든다면…

Page 43: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

public class UserBuilder { private String email; private String nickname; private String password; public UserBuilder withEmail(String email) { this.email = email; return this; } public UserBuilder withNickname(String nickname) { this.nickname = nickname; return this; } public UserBuilder withPassword(String password) { this.password = password; return this; } public User build() { return new User(email, nickname, password); }}

Page 44: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

public class UserTest { @Test public void canCreate() throws Exception { User user1 = new UserBuilder().withEmail("[email protected]").build(); User user2 = new UserBuilder().withEmail("[email protected]").withNickname("newname").build(); }}

22 장 . 복잡한 테스트 데이터 만들기 참고

Page 45: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

trait Fixture { def aSomeUser(email: String = "[email protected]", nickname: String = "nickName", password: String = "password") = new User(email, nickname, password)}

val user1 = aSomeUser val user2 = aSomeUser(email="[email protected]") val user1 = aSomeUser(nickName="newname")

Page 46: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

3. implicit 을 활용한 중복 제거

• 애플리케이션을 구현하다보면 반복적으로 전달하는 인자가 존재함 .• Scala 의 implicit 을 활용해 제거 가능

Page 47: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

class ClanService @Autowired() (val clanRepository: ClanRepository, val channelService: ChannelService) { def findClan(id: Long)(implicit user: User) = { [...] }

def create(name: String)(implicit user: User) = { [...] }}

Page 48: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

class ClanService @Autowired() (val clanRepository: ClanRepository, val channelService: ChannelService) { def findClan(id: Long)(implicit user: User) = { [...] }

def create(name: String)(implicit user: User) = { [...] }}

class ClanController @Autowired() (val clanService: ClanService) extends BaseController { @RequestMapping(value = Array(""), method = Array(RequestMethod.POST)) def create(name: String) = { val savedClan = clanService.create(name) new ClanDetailView(savedClan, currentUser) }

@RequestMapping(value = Array("/{id}"), method = Array(RequestMethod.GET)) def clanUsers(@PathVariable id: Long) = { val clan = clanService.findClan(id) new ClanDetailView(clan, currentUser) }}

Page 49: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

object CurrentUserDetails { implicit def cud: CurrentUserDetails = { currentUserDetails(SecurityContextHolder.getContext().getAuthentication().getPrincipal()) }

implicit def currentUser: User = { cud.getUser }}

import support.security.CurrentUserDetails._

class ClanController @Autowired() (val clanService: ClanService) extends BaseController { @RequestMapping(value = Array(""), method = Array(RequestMethod.POST)) def create(name: String) = { val savedClan = clanService.create(name) new ClanDetailView(savedClan, currentUser) }

@RequestMapping(value = Array("/{id}"), method = Array(RequestMethod.GET)) def clanUsers(@PathVariable id: Long) = { val clan = clanService.findClan(id) new ClanDetailView(clan, currentUser) }}

Page 50: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

• Case Class• Named Parameter• implicit

Page 51: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

• Pattern Match

• Some/Option

• Lambda( 람다 )

• 막강 Collection

• 모나드 등등 …

• Functional Programming

• Case Class• Named Parameter• implicit

Page 52: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Java 에서 Scala 적용 단계

Page 53: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala 를 적용 단계

• 1 단계 : Scala 를 자바처럼 구현한다 .• 2 단계 : 점차 Scala 문법에 친숙해지면 Scala 기능을 하나씩

적용한다 .• 3 단계 : 함수형 프로그래밍 스타일로 구현한다 .• 4 단계 : play 프레임워크로 갈아탄다 .

Page 54: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala, Spring-Boot, JPA 동거할 때 고려할 부분

Page 55: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala, Spring, JPA 동거시 불편한 부분

• Java 기반 프레임워크 사용하면서 고려할 부분이 생긴다 .• Scala 의 모든 기능을 극한으로 사용하는데 제약 사항이 있다 .

Page 56: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

@RestControllerclass UserController @Autowired() (val userRepository: UserRepository) { val Log = LoggerFactory.getLogger(classOf[UserController])

@RequestMapping(value = Array("/users"), method = Array(RequestMethod.POST) ) def join(@Valid @RequestBody userForm: UserForm, result: BindingResult) = { ... }

@RequestMapping(value = Array("/users/{userEmail}"), method = Array(RequestMethod.POST) ) def login(@PathVariable userEmail: String, @RequestParam(required = true) password:String) = { ... }}

Page 57: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

import java.lang.Long

import org.springframework.data.repository.CrudRepository

trait UserRepository extends CrudRepository[User, Long] { def findByEmail(email: String): User def findByEmailAndPassword(email: String, password: String): User def findByNickName(nickName: String): User}

Page 58: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

import java.util.{ArrayList, List}

@Entityclass Clan(pName: String) extends DomainModel { ...

@OneToMany(mappedBy = "clan", cascade = Array(CascadeType.PERSIST, CascadeType.REMOVE), fetch = FetchType.LAZY) val clanMembers: List[ClanMember] = new ArrayList[ClanMember]}

Page 59: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala 컴파일 시간

• Scala 의 가장 큰 단점은 컴파일 시간이 많이 소요된다 .• Scala 기반으로 개발하려면 SSD 는 필수 . 장비빨이 받쳐주어야

한다 .

Page 60: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

마치며…

Page 61: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

자바 기반의 MSA 를 고려한다면

Spring Boot + JPA

Page 62: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

자바가 싫증나거나 , OOP 와 FP 를 결합한 프로그래밍을 하고 싶다면

Scala

Page 63: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

Scala, Spring-Boot, JPA 동거 지속할거냐 ?

Page 64: Scala, Spring-Boot, JPA의 불편하면서도 즐거운 동거

1 년만 더…

1 년 후는 다음 세미나에서…