> For the complete documentation index, see [llms.txt](https://real-dev.gitbook.io/real-library/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://real-dev.gitbook.io/real-library/real-post/undefined.md).

# 나의 오픈소스 기여기 \[kotlin-JDSL]

#### 오픈소스 기여 같은 건 남의 일인줄만 알았지…

한적한 24년 6월의 어느 날. 저는 새로 제작중인 Kotlin 프로젝트에 Querydsl을 적용하기 위해 방법을 알아보고 있었습니다. 그런데 이게 생각보다 번거롭고, 포스팅과 환경에 따라 설정할 부분들이 더러 다르기도 해서 불편한 와중 어떤 포스팅 하나를 마주하게 됩니다.

<https://spoqa.github.io/2024/05/03/transfer-jdsl.html>

<br>

꽤 긴 포스팅이라 요약하자면 스포카에서 사용중인 QueryDsl을 Line에서 개발한 어떤 라이브러리로 대체하였고, 이게 꽤 괜찮았다라는 내용이었습니다. 마침 진행하는 프로젝트는 Kotlin 기반이고, QueryDsl은 적용하기 번거로웠습니다. 범용성이나 기술적 성숙도를 생각하자면 Querydsl이 올바른 선택일 수 있지만, 개인 프로젝트에 공부중인 기간이잖아요?

냅다 적용해보고 며칠간 사용해보며 장단점, 개인적인 팁들을 모아 블로그 포스팅으로 게시하기로 했습니다! 그렇게 모인 내용들은 아래와 같아요.

* **where()보단 whereAnd() 조건이 사용하기 편합니다.**
* **FETCH JOIN 요청 시, SELECT절에 엔티티를 명시하지 않아도 됩니다.**
* **Spring을 사용한다면 JqplRenderContext는 Spring Bean으로 등록해두는 쪽이 편합니다.**
* **JPQL로는 Paging을 구현할 수 없습니다. 등…**

<br>

정말 며칠 사용해보면 알 수 있는 내용들이었지만, 상대적으로 레퍼런스가 적은 라이브러리 특성 상 웹에서 쉽게 확인하기는 어려운 내용들이었고, 포스팅 게시 전 사전 검증 차원에서 [Github Kotlin-JDSL Repository](https://github.com/line/kotlin-jdsl)에 이슈로 등록했습니다. 스포카 포스팅에서도 언급된 내용인데, Kotlin-Jdsl의 제작자분께서 한국분이시고, 또 굉장히 빠르게 이슈에 대응해주신다는 이야기를 봤거든요.

포스팅에 게시될 내용들을 정리해서 크로스체크 목적으로 [Kotlin-JDSL Issue](https://github.com/line/kotlin-jdsl/issues/727)로 등록했고 제작자분의 답변 중 전혀 의외의 대답을 하나 듣게 됩니다!

<br>

***

#### 아니 이거 버그였어…?

라이브러리 제작자 분께선 소문처럼 정말 답변이 빠르시고, 나이스맨이었습니다…! Issue에 대한 대응도 빠르시고 친절하게 제가 게시한 내용들에 대해 설명해주시더라구요. 그 중 전혀 의외의 답변을 하나 받았습니다. 그건 바로

**제가 잘못 사용했기에 남들은 그러지 말라고 작성했던 내용이 알고보니** **버그였다 는 사실이었습니다.**

![Issue 중 일부](/files/A6UVPV2Z2vuofaxiaER7)

![정말 예상치 못했다.](/files/qkDCsCzEr4pM1c9AzWt9)

<br>

즉, ON절에서 `path(RedditTraslate::reddit).eq(entity(Reddit))` 처럼 엔티티 간 동등성을 비교할 수 없던 부분은 제작자 분께서 예상 못한 버그라는 답변을 받았습니다. 오픈소스 기여 과정이 궁금했는데 마침 잘 됐죠! 제작자 분께 제가 고쳐보겠다고 말씀드리고 작업에 착수합니다!

<br>

***

#### 생각보다 꽤 어려웠어요 :(

제작자분께서 조금 난이도가 있다고 말씀하신건 다 이유가 있었는데요. 일단 가장 먼저 내부 로직을 어느 정도 알고 있어야 한다는 점이었습니다.

Kotlin-JDSL 라이브러리의 역할은 Kotlin 코드로 작성한 DSL을 각 구문에 해당하는 Serializer가 JQPL로 변환해주는 라이브러리로 요약할 수 있습니다.

이 변환 과정에서 문장과 상태에 대한 정보를 담고있는 `JpqlRenderContext`에 각 DSL문이 어떤 문인지 (`SELECT, UPDATE, DELETE`…)나타내는 `JpqlRenderStatement` 와 어떤 절인지(`FROM, SET, WHERE` 등)를 저장하는 `JpqlRenderClause`가 포함되어 있고, 이에 따라 DSL 코드가 적합한 JQPL로 변환되는데 이걸 처음부터 파악하기는 어려웠거든요.

[이전 포스팅](https://real-dev.gitbook.io/real-library/v/real-post/debug)에서 언급했던 것처럼 디버거를 사용하여 꽤 한참 어떤 시점에 `RenderClause`가 변환되고, 참조되는지 확인했습니다.

![JpqlEntitySerializer.kr의 주요 코드](/files/GJ7Th26bn0Bw0x8yhi1X)

제가 제기하고 라이브러리 제작자 분께서 확인해주신 버그는 기존 라이브러리 코드에선 Join, On 절에 엔티티를 직렬화하는 과정이 별도로 분리되어있지 않았고, 자연스럽게 `RenderClause`가 `From`으로 전달되어 On절에서 엔티티를 직렬화할 경우 AS 키워드가 추가되어 발생하는 버그였습니다.

일단 가장 먼저 `JpqlRenderContext`에 Join, On Cluase를 추가해야했습니다. 기존엔 포함되지 않은 Cluase이고, **불필요할 수도 있나?** 하는 생각이 들었지만 Join/On절은 여러 번 반복적으로 작성될 수 있고, FROM절이나 Where 절과는 문법적으로 확실히 구분되어야 할 필요가 있었거든요.

> From절에선 엔티티 직렬화 시, AS Alias가 추가되고, Where절에서 엔티티 직렬화시에는 Alias만 추가되어야 합니다. 즉, **Join절은 From절과 동일하고, On절은 Where 절과 동일해요.**

때문에 `JpqlRenderContext` 에 새로운 절 유형인 Join, On Clause를 추가했습니다!

![](/files/RYCaShuTtN721K93UulT)

***

#### 여기서 끝이 아니다?!

네, 당연하게도 끝이 아니었습니다. Clause를 추가했다고 누가 알아서 상태를 바꿔주는 것도 아니고… JPQL에서 join()이 발생하는 시점엔 항상 Join Clause로 전환하고, On절에선 On Clause로 바꿔줘야죠! 생각보다 Join은 생각보다 많은 경우가 있었습니다.

![많다](/files/Bb5BE7zGS6dzmQqz5Bcw)

![의외로 코드는 별로 추가되지 않는다.](/files/Ig6JnlophYGVMvtMMM55)

<br>

이렇게 바꾸고 테스트를 해보니…? 당초에 제가 생각했던 것처럼 On절에서 Entity를 이용한 Predicate 구성이 원활히 수행되었습니다. 해당 예제 코드는 사실 원래 프로젝트에 포함되지 않았지만 제가 추가한 코드입니다! from절 내부에서 join을 수행할 때 predicate를 만드는 요소로 BookPublisher에 포함된 book 엔티티와 Book 엔티티 클래스를 동등 비교하여 조인하는 조건입니다.

기존엔 `path(BookPublisher::book).path(Book::id).eq(path(Book::id))` 와 같은 식으로 비교해야했지만, 엔티티를 기준으로 비교할 수 있도록 개선했습니다!

![이제 join,on절에서 문제없이 엔티티를 직렬화할 수 있다.](/files/MgHt14AOo0Hh4oFYuDRu)

<br>

***

#### 여기서 끝이 아니다?!

네, 당연하게도 끝이 아니었습니다. 코드를 잘 알맞게 고쳤다쳐도(~~심지어 빼먹은 부분도 있었고~~) 일단 오픈소스 기여 자체가 처음이라 Rule이 익숙하지 않은 점들도 꽤 어려웠습니다.

* CommitLint: 커밋 메시지는 정해진 규격을 따라야하며, 소문자로 작성해야한다.
* PR 규칙:
  * Bugfix PR은 develop 브랜치가 아닌 main 브랜치를 base branch로 지정해야한다.
  * (당연하게도) main 브랜치에 반영하지 않을 develop 브랜치의 내용이 PR에 포함되어서는 안 된다.

<br>

이러한 부분들은 저는 전혀… 모르고 있었고 오픈소스 Discord에 진입하는 방법도 전혀 몰랐습니다.

![어떻게 특정 채팅방에 접속하는지 아시겠나요…? 사실 이게 제일 어려웠](/files/ci6SicuPs1hZXY7iW8fy)

그래서 저는 간신히 디스코드 채팅방에 접속도 하고, commit message도 수정하고, ~~브랜치 관리를 잘못하고, 수정 사항 중 추가해야할 부분이 있어서~~ PR를 다시 올리며!! 마침내!!

<br>

![](/files/nxtqrJgX35FwAFzyHsi2)

***

<br>

![](/files/rXDRvtu5nhqAs1g2rS3e)

<br>

드디어 PR이 Merge되었습니다…! 며칠간의 노력이 빛을 발하는 순간이었고… 제가 작성한 코드가 오픈소스 버전으로 반영되어 누군가 사용하게 되는 순간이었습니다…!!

***

#### 대단하지 않아도

이로써 저의 첫 오픈소스 기여가 마무리 되었습니다…! 만 사실 뭔가 엄청나게 큰 변화는 없네요! 라이브러리의 버전이 하나 올라갔고, 버그가 하나 고쳐졌습니다.

음… 그래도 괜찮았던 것 같습니다. 라이브러리가 동작하는 원리를 이해하려 노력했고, 제법 잘 이해했고, 디스코드를 통해 확인해가며 작업의 방향성을 잘 확인한 것도 괜찮았던 것 같고요. ~~그리고 라이브러리 제작자분 진짜 친절하시고…~~

무엇보다 제작자분께서 계속 도움을 주셔서 감사하다고 말씀해주셨는데, 저도 이런 기회에 주어져서 감사하고 기쁘더라구요. 오픈소스 기여라는게 공공을 위한 해도 좋은 일이지만, 자신에게 돌아오는 가치가 훨씬 크다고 느꼈구요.

즐거운 시간이었고, 앞으로도 기회가 된다면 지속적으로 오픈소스에 기여해볼 생각입니다. 여러분도 너무 거창한 일이라 생각하지 않고 작게라도 기여를 시작해보시는건 어떨실까요? 분명 즐거운 경험이 될 겁니다!

지금까지 Real-Dev, 나의 오픈소스 기여기였습니다!

<br>
