오늘 매니저님과 수다를 떨다가 어떤 팀은 오전 9시-10시를 출석만 찍고 추가로 쉬고온다고 얘기를 해주셔서 너무나도 솔깃했다.
그래서 곧장 우리 팀은 식사 시간을 30분씩 앞에 추가하는 걸 제안드렸는데 일단 자율적으로 해보자고 하셔서 진행했더니 솔직히 아주 편하고 좋긴 했다.
놀러온 건 아니지만 식사 시간에 요리랑 설거지까지 마치면 남는 시간이 너무 부족해서 커피 한 잔 사올 여유를 가지기도 힘들었는데 이번엔 확실히 여유롭게 쉬는 시간까지 가지고 리프레쉬해서 공부할 수 있었다.
결국 12시간을 진짜로 다 공부할 수 있는 사람 아니고서야 이 정도는 휴식을 취하면서 템포를 맞춰가는게 좋지 않을까? 일단 나는 12시간은 커녕 집중해서 6시간 땡기기도 벅차다.
다행히도 제안은 팀원 분들과 매니저님께 모두 컨펌받아 오늘 바로 적용했고 다들 전체적으로 만족하셨고 학습 분위기도 딱히 저해되는 것 같진 않다고 해주셔서 잘 정착하게 될 것 같다.
입문 퀴즈 8문제를 풀고 제출했는데 모호한 선택지를 완벽히 구별해냈다기 보단 한 80% 느낌으로 맞춘 문제도 있어서 완벽하다곤 못하겠으나 얼추 다 이해하고 제출했기에 부끄러움은 없었다.
그것보다는 내일부터 들어가는 숙련 단계의 Spring에서 얼마나 많은 키워드와 문법이 나를 반길지가 참 걱정이고 구조적인 부분부터는 써본 경험만 있지 코딩해보는 건 처음이니 이해에 많은 어려움을 겪지 않을까 싶다.
그리고 베이직, 스탠다드, 챌린지의 분반은 처음 골랐던 대로 챌린지 반에 배정받게 됐다. 아직도 이게 맞나? 싶어 긴가민가한데 문법에 어려움을 겪는 단계는 아니니까 챌린지에서 굴러봐야 갈피를 잡고 새 지식을 탐구하든 방향을 꺾든 할 이유를 찾을 것 같으니 일단은 열심히 해야겠다.
같이 배정받으신 분들은 앞선 과제로 이미 한번 평가되기도 했고 나 말고도 전공이나 전직 경험이 있으시거나 공부를 즐기시거나 목표가 확실하신 분들도 많은 것 같아서 정말 잘하실 것 같은데 나도 늦장부릴 때는 아닌 것 같고 수업을 들어봐야 알 것 같다.
Domain Driven Design 같은 방법론은 TDD 문서도 읽어봤고 하니 이 방법론이 뭘 지시하는 지만 알면 어느정도 알 거라 생각했는데 생각보다 디테일해서 이해하는 데에 시간을 좀 쓴 것 같다.
일단 설계 단계에서 가장 크게 작용한다고 보면 될 것 같은데 Actor, Event, Command, Policy, External System, Hotspot, Aggregate, Bounded Context 같은 새로운 키워드가 많아서 좀 어지러웠지만 정말 다행히도 실무에서 어깨 너머로 보던 테이블 구조, API 문서들이 구분되는 구조랑 비슷해서 개념적으로는 달달 못외우더래도 이유와 느낌을 이해하는데에는 도움이 됐다.
그 중에서도 Domain 설계의 핵심이 되는 테이블 구조에 대해서는 더 복잡하게 데이터를 받아서 써보기도 했으니 직접 짤 때 엉성하긴 하겠지만 그 이유를 몰라 의심하는 일은 적게 발생할 것 같다. 일단 튜터님도 있고 팀원이나 챗봇의 도움도 있을 수 있으니 설계의 상상력을 발휘하는 게 관건이 될 것 같다.
키워드가 달라져서 그렇지 Domain, Entity를 Class와 객체같은 개념으로 접근하면 서버에서도 방법론이 존재하는 이유와 설계/동작 구조가 유사함을 이해할 수 있었다.
다만 Aggregate, Bounded Context는 들어도 좀 포괄적인 개념에다가 저마다 해석에 따라 달라져서 문서상 정리를 위해서라도 조금 더 이해하려고 노력해봐야 할 것 같다.
요약: TDD가 테스트를 먼저 정의하듯 요구사항을 이해하고 정리하며 Domain을 먼저 명세하고 설계하는 방법론이라 볼 수 있겠다.
DDD 아키텍처 패턴 계층
- Presentation Layer (UI/API)
API endpoint를 제공하고 사용자 입력을 받아 Application 서비스를 호출하거나 데이터를 반환함, Spring 에서 정의할 @RestController 가 여기 포함됨
- Application Layer
Application 서비스와 DTO를 사용하고 Domain 레이어와 통신하며 Presentation과 Domain 사이에서 데이터를 중개함
참고한 다른 설명에 오케스트레이션이라는 키워드가 있었는데 Domain 레이어 여러개의 오퍼레이션을 Application 레이어에서 조율하고 통합한다는 의미로 사용함
- Domain Layer (UI/API)
핵심 비즈니스 로직과 도메인 모델 (Entity, Key-value, Aggregate, Domain service 등)을 사용하고 Application의 상태와 행위, 규칙을 구현함
다른 레이어에 대한 의존성이 없어야 한다 (다른 레이어를 직접 참조하거나 의존해서는 안된다)
- Infrastructure Layer (UI/API)
DB 연동, 외부 시스템 연결, 기술적인 교차 관심사를 처리하고 Domain 레이어의 인터페이스를 구현함
기술적인 교차 관심사는 비즈니스 로직과는 직접 관련이 없지만 여러 모듈, 계층에 걸쳐 사용되는 기능을 의미함 (로깅, 보안, 캐싱, 모니터링, 예외처리, 트랜잭션 관리 같은 기능)
위 기능들은 로직과는 분리되어 있지만 다른 레이어에서도 쓸 수 있기 때문에 Infra 레이어로 분리를 한 것 (관심사의 분리 원칙에 따라 교차 관심사를 별도 계층 또는 모듈로 분리함)
REST 규칙을 완벽하게 지키는 RESTful API는 정말 어렵다는 말은 언제 한 번 들어본 적 있긴 한데 강의에서 나온 REST API 예제만 봐도 실무에서 쓰던 거랑 딱히 다를 바가 없어서 이 정도면 충분한 거 아닌가?
라는 생각이 들었었다.
실제로 보니 RESTful의 구현 레벨을 Level 0 ~ 3까지 나눴을 때 Method와 URI로 구분하는 Level 2로 이루어진 구성은 익숙한데 다음 상태까지 지정하는 Level 3에서 확 복잡해지는 느낌을 받기도 했다. Level 2는 어쨌든 문서를 달달달 보면서 개발했었는데 Level 3는 이상적이라 좋긴 한 것 같다.
다만 실제로 쓰이는 걸 본적은 없는데 강의에서도 Level 3를 철저히 지키며 개발하면 개발 리소스가 너무 커지고 API Document는 잘 정리되기 시작하면서 Level 2에서 끊는 경우가 많다고 한다.
그리고 API를 설계할 때 Command에 따라 URI, Method만 고민하지 말고 Status code까지 함께 적어 시나리오를 같이 고민해주는 게 좋다. 그 외에는 실무에서 보던 느낌 그대로 진행해도 올바른 설계가 될 수 있다.
URI에 동사가 들어가는 것도 지양하는 걸 권장하지만 URI에 동사로 지정해서 프론트에서 요청이 간단해진다면 하는 게 좋을 것 같다. 앱 할때 Body 짜다가 설계도 바꾸고 데이터 더 끌어오고 하다보니 복잡해져서 그렇게 느꼈었다.
인증의 경우 /sessions
처럼 지정해 설계하는 것이 아닌 login
처럼 REST 규칙을 깨고 그냥 명시적인 URI로 표기하고 POST, PUT을 사용하는 것도 자주 있는 케이스같고 그 쪽이 더 문서로도 읽기 편하다고 생각한다. user 정보를 수정할 때는 User domain에 민감정보까지 있더래도 비번, 아이디같은 민감정보 제외한 /users/{id}/profile
처럼 프로필 정보만 분리해서 PUT을 설정해줌이 좋다.
여담이지만 이런 문서 이슈, 통신 이슈를 보다보면 GraphQL을 사용할 때 느꼈던 편리함이 이런 데서 찾아오는거구나 싶은 생각이 든다. 물론 GraphQL은 항상 Scheme 관리를 해야했긴 한데 대신 관리만 잘 되면 Type을 명시적으로 사용하기 좋아서 데이터 검증이 정말 좋았다.
옛날에는 Postman을 통한 테스트만 했었는데 문서화에 치중되어 있는 Swagger를 많이 쓰는 추세가 됐다고 한다. 여전히 프론트가 하는 API 테스트에는 Postman이 무난할 것 같긴 한데 개인적으로 GQL Doc을 겪어본 입장에선 Swagger로 배포가 가능하다면 더욱 스키마를 확인하기 용이하다고 생각한다.
추가로 Bean 생명주기를 아직 강의에서 다 배운건 아닌데 @SpringBootApplication 어노테이션이 있는 클래스부터 시작해서 @Configuration 어노테이션 클래스가 먼저 로드된다고 한다. 어플에서 보는 생명주기랑은 또 다른 느낌이라 꽤 신기했고 오히려 앱 생명주기보다 관리가 쉽나? 하는 생각도 드는데 파일이 많아질 테니 패키지 관리는 오히려 앱 패키지보다 잘 해야겠다는 생각이 들었다.
어쨌든 Swagger Title과 Description을 기준으로 @Configuration 어노테이션과 생명주기를 약간 엿봤고 다른 설정이나 라이브러리 관리에도 이렇게 쓰이겠구나를 알았다.
그리고 문서화, 테스팅 관련한 라이브러리인 만큼 Infrastructure 레이어에 속하고 패키지로는 infra.swagger 같이 관리하는 요령이 있었다.
앱에서는 SDK 기반에 스토어 배포도 따로 잘 되어있던 만큼 Infra를 별도로 관리할 일이 있었나 잘 기억이 안나는데 일단 백엔드에선 로직, 도메인과 직접 관련 없는 내용도 많이 다룰 것 같으니 위에서 정리한 계층화도 잘 염두해둬야겠다.
2016년 1월 1일은 금요일입니다. 2016년 a월 b일은 무슨 요일일까요? 두 수 a ,b를 입력받아 2016년 a월 b일이 무슨 요일인지 리턴하는 함수, solution을 완성하세요. 요일의 이름은 일요일부터 토요일까지 각각 SUN,MON,TUE,WED,THU,FRI,SAT
입니다. 예를 들어 a=5, b=24라면 5월 24일은 화요일이므로 문자열 "TUE"를 반환하세요.
fun solution(a: Int, b: Int): String = LocalDate.of(2016, a, b).getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.US).toUpperCase()
문제를 보자마자 2016년 1월 1일의 요일 정보와 윤년같은 정보를 보고 알고리즘 테스트인 만큼 % 7로 구하는 방법이 먼저 떠오르긴 했는데 앱에서 수많은 Date를 처리를 해봤다보니 굳이?
라는 생각이 너무 강하게 들어서 그냥 LocalDate 써서 풀었다.
TextStyle에 뭐가 있는 걸 전부 자세히 아는 건 아니지만 Date를 잘라서 쓰든 String으로 짬뽕하든 워낙 많이 해봤기에 짧은 텍스트(Tue)로 불러와서 대문자 처리만 했다.
제출 점수도 10점이 나온걸 보면 비합리적인 풀이는 아니었던 것 같은데 제한 조건에 다른 모듈을 갖다 쓰지 말라던가 하는 내용도 없었으니 딱히 말이 안되는건 아니지 않을까?
물론 퍼포먼스는 생각보다 안좋게 나온걸로 보아 Localdate를 불러오는 건 시스템 사양에 따라 항상 달라질 수 있음은 염두에 둬야겠다.
그래도 Kotlin스럽게 풀이한 것도 공부는 해둬야 하니 다른 사람의 풀이를 참조하기로 했다.
fun solution(a: Int, b: Int): String {
val week = listOf("THU", "FRI", "SAT", "SUN", "MON", "TUE", "WED")
var answer = ""
var days = b
for (i in 1 until a) {
days += when (i) {
1, 3, 5, 7, 8, 10, 12 -> 31
2 -> 29
else -> 30
}
}
answer = week[days % 7]
return answer
}
When 으로 주어진 월에 따른 일수를 계산해서 b가 먼저 저장된 days에 지나간 월의 일수를 더하고 최종적으로 2016년 1월 1일 + 일수를 7의 나머지로 week text를 가져오는 식의 풀이였다.
나도 이렇게 풀었다면 아마 b를 더할 때 정확한 일수가 맞는지 고민해서 1을 더해야하나 빼야하나 냅둬야하나 부터 고민중이었을 것 같다. 이럴 때 테스트 케이스를 2~3개 더 추가해서 a, b 값을 좀 늘려서 대충 테스트 실행해봐야 좋은 공부인가 싶기도 하다.