scala, spring-boot, jpa의 불편하면서도 즐거운 동거
TRANSCRIPT
Scala, Spring-Boot, JPA 의
불편하면서도 즐거운 동거
Scala, Spring-Boot, JPA 가 동거를 시작한 이유는 ?
JPA 가 동거에 기여하는 점
Spring-Boot 가 동거에 기여하는 점
Scala 가 동거에 기여하는 점
Scala, Spring-Boot, JPA 동거할 때 고려할 사항
Scala, Spring-Boot, JPA 가 동거를 시작한 이유는 ?
JPA• 단순 , 반복적인 CRUD 작업하기 싫다 .• 스키마 변경에 따른 비용을 최소화하고 싶다 .• 핵심 비즈니스 로직에 집중하고 싶다 .
Spring Boot• 단순 , 반복적인 Spring 설정 작업하기 싫다 .• 핵심 비즈니스 로직에 집중하고 싶다 .
Scala• 10 년 이상 Java 만 했더니 재미없더라 .• 2 년 정도 함수형 언어로 Scheme, Clojure 도전했다가 중도 포기
• 어딘가 직접적으로 활용하고 있지 않으면 동기부여가 잘 되지 않는
성격
• 단순 , 반복적인 작업 제거
• 핵심 비즈니스 로직에 집중
• Java 에 싫증이나 Scala 를 선택
• 단순 , 반복적인 작업 제거
• 핵심 비즈니스 로직에 집중
• Java 에 싫증이나 Scala 를 선택
Play 가 아닌 Scala + Spring + JPA 조합을
선택한 이유
• Spring 과 JPA 경험을 버리는 것의 아쉬움 .• 중도 포기하지 않으려면 변화를 Scala 하나로 한정 .• 변화를 싫어하는 다른 Java 개발자를 설득하기 위해 최소한의 변화 .
JPA 가 동거에 기여하는 점
JPA• 단순 , 반복적인 CRUD 작업하기 싫다 .• 스키마 변경에 따른 비용을 최소화하고 싶다 .• 핵심 비즈니스 로직에 집중하고 싶다 .
JPA 도입에 따른 더 큰 효과요구사항이 자주 변경되는 프로젝트 초반 , 빠른 사이클로 설계에
대해 다양한 실험이 가능하다 .
JPA 를 사용하지 않는 경우 – 테이블 최초 생성시
데이터베이스에 스키마 적용
비즈니스 로직 구현 테이블 설계
테이블 스키마 생성SQL 쿼리 구현
자바 테이블과 매핑되는 객체 추가
1
2
34
5
6
JPA 를 사용하지 않는 경우 – 테이블 스키마 변경시
데이터베이스에 스키마 적용
비즈니스 로직 변경 ( 선택 )테이블 설계
테이블 스키마 변경칼럼 변경에 따른 SQL 쿼리 수정
자바 객체에 필드 변경
비즈니스 로직 구현 객체 설계
JPA 를 사용하는 경우 – 객체 (Entity) 최초 생성시
객체 추가 및 매핑
데이터베이스에 스키마 적용
비즈니스 로직 구현 테이블 설계
테이블 스키마 생성SQL 쿼리 구현
자바 테이블과 매핑되는 객체 추가
1
2
3
비즈니스 로직 구현 (선택 )
객체 설계
JPA 를 사용하는 경우 – 객체 (Entity) 필드 변경
객체 필드 변경
데이터베이스에 스키마 적용
비즈니스 로직 변경 ( 선택 ) 테이블 설계
테이블 스키마 변경칼럼 변경에 따른 SQL 쿼리 수정
자바 객체에 필드 변경
JPA 도입 효과• 요구사항이 자주 변경되는 프로젝트 초반 빠른 구현 – 피드백
사이클
• 빠른 피드백 사이클은 삽질할 수 있는 시간을 확보함으로써 빠른 지식
축적이 가능하다 .• 지식 축적은 도메인에 최적화된 설계를 할 수 있도록 한다 .• 좋은 설계는 사용자의 요구사항 변화에 빠르게 대응할 수 있다 .• 개발자는 소스 코드에 대한 자부심과 여유 시간을 확보할 수 있
다 .
더 자세히 알고 싶다면…ORM 프레임워크를 활용할 때의 설계, 개발 프로세스
Spring-Boot 가 동거에 기여하는 점
Spring Boot• 단순 , 반복적인 Spring 설정 작업하기 싫다 .• 핵심 비즈니스 로직에 집중하고 싶다 .
1. @IntegrationTest• Spring Boot 는 Embedded Tomcat 기반으로 동작 .• @IntegrationTest 기반으로 테스트하면 Embedded Tomcat 을
자동 실행한 후 Client 테스트가 가능 .• 특히 RestTemplate 을 활용해 API 테스트할 때 유용 .
지금까지 통합 테스트 방법
WAS 시작
통합 테스트 실행
@IntegrationTest 기반 API 테스트 방법
Only 통합 테스트 실행= WAS 시작 + 통합 테스트 실행
@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}
@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) }}
2. Spring Boot Actuator• 서비스 운영시 모니터링 요소에 대한 기본 정보 제공
제공하는 정보• 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 에서 추가 정보 확인
Application Detail
출처 : https://github.com/codecentric/spring-boot-admin
Thread 상태
출처 : https://github.com/codecentric/spring-boot-admin
Actuator Remote Shell
출처 : http://nomimic.tistory.com/5
빠르게 최소한의 모니터링 및 운영 환경을 구축할 수 있다 .
Scala 가 동거에 기여하는 점
Spring Boot + JPA 환경 조합만으로 충분히 생산성 높다 .
Scala• 10 년 이상 Java 만 했더니 재미없더라 .• 2 년 정도 함수형 언어로 Scheme, Clojure 도전했다가 중도 포기
• 어딘가 직접적으로 활용하고 있지 않으면 동기부여가 잘 되지 않는
성격
1. Domain 과 DTO 의 명확한 분리에 대한 거부감이
줄어듦
• 현재 개발 추세는 Domain 객체와 DTO 에 중복되는 부분이 많아 자바
객체 하나가 Domain 역할 , DTO 역할을 하는 방식으로 구현 .• Scala 를 활용하면 각 역할별로 구현하는 것에 대한 거부감이 줄어듦
@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 를 생성하지 않아도
된다 .
@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 추가함 .
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 자동
추가
Domain 과 DTO 의 명확한 분리에 대한 거부감이
줄어듦
• 분리하는 것이 항상 좋은 것은 아니다 .• 상황에 따라 Entity 와 DTO 를 분리 / 통합할 것인지에
대한 역량을 키우는 것이 더 중요하다 .
2. Test Fixture(Test Data) 생성하기 용이함 .• 자바에서 Test Fixture 를 생성하고 변경하기 어려움은 Test 코드를
만드는데 약간의 장애물이다 .• Scala 는 named parameter 를 통해 해결 가능
email, nickname, password 를 가지는 User 객체에 대한 테스트를 데이터를 만든다면…
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); }}
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 장 . 복잡한 테스트 데이터 만들기 참고
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")
3. implicit 을 활용한 중복 제거
• 애플리케이션을 구현하다보면 반복적으로 전달하는 인자가 존재함 .• Scala 의 implicit 을 활용해 제거 가능
class ClanService @Autowired() (val clanRepository: ClanRepository, val channelService: ChannelService) { def findClan(id: Long)(implicit user: User) = { [...] }
def create(name: String)(implicit user: User) = { [...] }}
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) }}
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) }}
• Case Class• Named Parameter• implicit
• Pattern Match
• Some/Option
• Lambda( 람다 )
• 막강 Collection
• 모나드 등등 …
• Functional Programming
• Case Class• Named Parameter• implicit
Java 에서 Scala 적용 단계
Scala 를 적용 단계
• 1 단계 : Scala 를 자바처럼 구현한다 .• 2 단계 : 점차 Scala 문법에 친숙해지면 Scala 기능을 하나씩
적용한다 .• 3 단계 : 함수형 프로그래밍 스타일로 구현한다 .• 4 단계 : play 프레임워크로 갈아탄다 .
Scala, Spring-Boot, JPA 동거할 때 고려할 부분
Scala, Spring, JPA 동거시 불편한 부분
• Java 기반 프레임워크 사용하면서 고려할 부분이 생긴다 .• Scala 의 모든 기능을 극한으로 사용하는데 제약 사항이 있다 .
@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) = { ... }}
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}
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]}
Scala 컴파일 시간
• Scala 의 가장 큰 단점은 컴파일 시간이 많이 소요된다 .• Scala 기반으로 개발하려면 SSD 는 필수 . 장비빨이 받쳐주어야
한다 .
마치며…
자바 기반의 MSA 를 고려한다면
Spring Boot + JPA
자바가 싫증나거나 , OOP 와 FP 를 결합한 프로그래밍을 하고 싶다면
Scala
Scala, Spring-Boot, JPA 동거 지속할거냐 ?
1 년만 더…
1 년 후는 다음 세미나에서…