나도 확실하게 잘 다루는 서버 Framework를 하나 가지고 싶은데,
뭐로 할까 고민하다가 대중적이면서도 내가 현재 개발할때 익숙한 Java/Kotlin을 지원하는
Spring을 배워보자는 마음을 먹었다.
"kotlin spring boot"이라고 검색하면 말은 느리지만 손은 빠른 아저씨의 유튜브 강의 시리즈가 나오는데,
이걸 보고 공부하고 있다. 이제...10강!까지들었다.
아직 찍먹한지 겨우 이틀되었지만 까먹기 전에, Spring에 관해 알아낸 것들을 간략히 정리 해 보겠다.
Spring Framework vs Spring Boot
https://spring.io/projects 에 가면 여러가지 Spring "프로젝트"라고 불리는 것들이 보인다.
이것은 Spring이라는 Framework에 속하거나, 갖다 붙일 수 있는 것들인데 이 중의 하나가 Spring Boot다.
Spring이 인기를 얻게되고, Spring Framework에도 여러가지 제품들이 생겨나고 붙게되면서
Spring으로 초기 프로젝트를 세팅하는 것이 까다로워졌다.
그래서 간단하게 Spring Application을 설정하고, 개발자들이 바로 개발을 시작할 수 있도록 Spring Boot가 등장했다. Spring Boot는 내장된 Tomcat과 "스타터" 라이브러리들의 의존성 관리를 단순화 시켜준다.
MVC Architecture
Model - View - Controller로 구성되는 MVC 아키텍쳐가 보편적으로 쓰이는 것으로 보인다.
Controller는 Spring에서 제공하는 @RestController Annotation을 붙여서 만들어두면,
Spring 내부적으로 Dispatcher Servlet이라는 녀석이 HTTP 요청이 들어올때, 이를 처리할 적절한 Controller를 찾아서 처리한다. 때문에 직접적으로 코드상에서 Controller를 호출하는 코드는 없다.
Bean
강의에서는 아직 무엇인지 가르쳐주지 않았지만, 다른 개발블로그들에서 자꾸 빈빈 거려서 찾아봤다.
빈(Bean)은 스프링 컨테이너에 의해 생명주기가 관리되는 Java 객체들이다.
앞서 예시로든 @RestController
도 빈이다.
@RestController
구현부를 따라들어가면 이녀석은 @Component
어노테이션이 붙어있는데,
이 @Component
를 사용하는 모든 곳을 Spring은 Scan하여 관리한다.
의존성 주입: @Autowired
@Autowired
라는 어노테이션을 붙여 객체에 대한 의존성을 주입할 수 있는데,
이때 객체는 빈(Bean)이어야 한다. Spring이 이미 빈에 대해서는 다 스캔을 해뒀으니 의존성 주입이 가능해진다.
Test: JUnit + Mockk
사실 TDD에 많이 회의적이었다. 변명이긴 하지만 기간에 쫓기다 보니 모든 것을 테스트 가능하게 한다는게 꿈같은 얘기같았다. 그런데 강의에서 API 하나 딸때마다 테스트를 2개씩 (행복편, 불행편) 만들어대서 쭉 따라가봤는데 내 코드에 신뢰감이 드는게 짜릿했다. 특히, MVC의 M에 해당하는 Service단에서 테스트하는 것보다 Controller 테스트가 인상적이었다.
@SpringBootTest
@AutoConfigureMockMvc
위 어노테이션들을 붙여주면 Application의 설정과 등록된 모든 빈(Bean)을 모킹하여 테스트를 시작할 수 있다.
@Test
fun `should add the new bank`() {
// given
val newBank = Bank("abc1234", 31.415, 2)
// when
val performPost = mockMvc.post(baseUrl) {
contentType = MediaType.APPLICATION_JSON
content = objectMapper.writeValueAsString(newBank)
}
// then
performPost
.andDo { print() }
.andExpect {
status { isCreated() }
content {
contentType(MediaType.APPLICATION_JSON)
json(objectMapper.writeValueAsString(newBank))
}
}
mockMvc.get("$baseUrl/${newBank.accountNumber}")
.andExpect {
content { json(objectMapper.writeValueAsString(newBank)) }
}
}
위 코드는 은행 계좌를 추가하는 부분을 테스트한다.
HTTP 상태코드와, Content Type, 그리고 응답까지 체크 가능한 부분이 매우 흥미로웠다.
그리고 유튜브 강의 선생님이 Test할때 `should ~` 문을 사용하여 테스트 케이스를 문장으로 정의하고 내용은 given / when / then으로 구조를 명확히 구분해두었는데 좋은 테스트 코드를 작성하게 해주는 꿀팁인듯하다.
Persistence: JDBC / MyBatis / JPA / Hibernate
강의에서 DB를 다루지는 않았지만, DB연결은 어떻게 하지? 찾아보다가 여러가지 키워드를 발견했다.
첫번째는 JDBC다. Java Database Connectivity의 약자로, 말그대로 Java 앱에서 DB 연결을 관리해주는 드라이버다. 이것만으로도 개발할 수 있긴하지만, 추상화 수준이 낮아 쿼리를 직접 작성해야하고, 오브젝트 매핑도 직접하고, 연결관리도 하고 기타 등등 번거로운 일들이 많아진다.
MyBatis는 여러번 들어본 적있긴한데 그것도 거의 10년전이라... SQL Mapper라고하는데 ORM이랑은 달리 SQL을 Object와 매핑하는 것이 SQL Mapper라고한다. JDBC에 비하면 추상화 수준도 높아서 써봄직한데 요즘은 Hibernate를 많이 사용하는 추세인듯하다.
JPA는 Java Persistance API의 줄임말로 ORM 기술에 대한 API 명세일뿐이다.
이 JPA를 기반으로 많은 구현체들이 만들어져있는데, 그 중의 하나가 Hibernate다. 따라서 Hibernate는 ORM 라이브러리다. ORM을 쓰면 개발이 빠르고 편해지는 점은 있지만, 나중에 스케일이 커지고 성능 튜닝이나 복잡한 쿼리를 필요로할때 큰 짐이된다. 그래서 바로 Hibernate로 시작하기보다 JDBC, MyBatis, Hibernate 3가지 방법 다 사용해보고 각자의 장/단점을 익혀두는게 좋을 것 같다는 생각이든다.