Dev

모듈을 만들 때 염두에 두어야 할 점

친환경사과 2025. 4. 7. 18:13

🍎 서비스의 기능을 모듈로 개발하거나 기존의 기능을 모듈로 떼어낼 때 주의할 부분을 정리합니다.


🍏 모듈로서 갖춰야 할 조건

✓ 서비스 개발 시 어떤 기능을 모듈로 떼어내 원자적으로 구성할지 생각해봐야 합니다.

✓ 결제 서버(= 레포지토리) 안에 존재하는 결제 검증 기능이 API 서버 혹은 인증 서버에 필요할까요? 그렇지 않습니다.

✓ 특정 서버에 종속되지 않고 공통적으로 사용되는 기능은 모듈화 하기에 적합합니다.

Ex_> Logger, Client Library 등등

 

🍏 정확한 사용자 정의

모듈을 이용하는 사용자는 API 서버를 호출하여 응답값을 얻는 Client(Frontend)가 아닌 내부 서버 개발자(platform, product 등등)입니다. 내부 개발자가 모듈을 효과적으로 활용할 수 있도록 개발 시 아래 두 가지를 생각해야 합니다.

 

✓ 쉬운 사용

사람마다 쉽다고 생각되는 사용이 다를 순 있지만 제가 정의하는 쉬운 구조는 "사용 시점에서 로직의 흐름을 깨지 않고 진행할 수 있음"입니다.

 

☑️ 흐름을 깨는 코드

// seedMap
val map = mapOf("a" to 3, "b" to 1, "c" to 2)

// 별도의 Comparator 객체를 먼저 생성
val valueComparator = Comparator<Map.Entry<String, Int>> { entry1, entry2 ->
    entry1.value.compareTo(entry2.value)
}

// entries를 리스트로 변환
val entriesList = map.entries.toList()

// 변환된 리스트에 대해 정렬 수행
val sortedEntries = entriesList.sortedWith(valueComparator)

// 정렬된 entries를 다시 Map으로 변환
val resultMap = LinkedHashMap<String, Int>()
sortedEntries.forEach { resultMap[it.key] = it.value } // result: {b=1, c=2, a=3}

 

✅ 흐름을 깨지 않은 코드

val sortedMap = map.entries.sortedWith(
    compareBy<Map.Entry<String, Int>> { it.value }
        .thenBy { it.key }
) // result: {b=1, c=2, a=3}

 

Kotlin에서 Map을 정렬할 때를 생각해 보겠습니다.

흐름을 깨지 않는 코드는 사용자의 입장에서 별도의 복잡한 설정이나 중간 단계 없이 직관적으로 사용할 수 있습니다.

그러나, 흐름을 깨는 코드의 경우 원하는 결괏값에 도달하기까지 Comparator 생성, 리스트 변환 그리고 다시 Map으로 변환하여 여러 번의 맥락 전환이 필요합니다.

모듈을 로직 흐름을 고려하지 않은 채 만들게 되면 코드 가독성이 낮아지고 보일러 플레이트한 코드가 증가합니다.

 

✓ 명확한 주석

명확한 주석은 아무리 강조해도 지나침이 없습니다. 모듈 사용 시 사용 설명서라고 생각하면 됩니다.

kotlin.collections sortedWith 함수

sortedWith() 함수를 설명란엔 함수의 목적과 반환값을 설명하고 정렬 방식의 특성, 그 의미까지 추가해 사용자가 함수의 동작 방식을 이해할 수 있게 돕습니다.

주석을 통해 올바른 모듈 사용 안내, 예상되는 동작과 결과, 모듈 사용 시 주의 사항 등 알릴 수 있습니다.

 

🍏 조직 내의 합의된 패키지 경로

모듈이 개발되는 과정을 생각해 보면 처음부터 독립적인 저장소에서 개발될 경우도 있겠지만 개발 중인 저장소(ex, API 서버)에서 개발이 진행되고 완성된 후 분리되는 모습도 볼 수 있습니다.

✓ 되도록이면 모듈을 나누게 될 때, Package 경로를 Static 하게 지정하고 개발하는 것이 좋다고 판단합니다.

 

🥕 동일한 저장소에 있는 모습

indie.blog.article.common.model.User // 모듈 기능
indie.blog.article.common.model.DateUtil // 모듈 기능
indie.blog.article.service.UserService

위와 같은 Package 구조를 갖고 있을 때

package indie.blog.article.service;

import indie.blog.article.common.model.User;
import indie.blog.article.common.model.DateUtil;

public class UserService {
    public User createUser(String name) {
        User user = new User(name);
        user.setCreatedAt(DateUtil.getCurrentDate());
        return user;
    }
}

UserService는 위와 같이 사용될 수 있습니다.

 

☑️ 독립된 저장소로 이동 시 다른 경로로 구조를 잡을 경우

indie.common.User // 모듈 기능
indie.common.DateUtil // 모듈 기능

 

위와 같이 변경할 경우 참조하고 있는 모든 import 문을 변경해야 합니다. 또한, 이미 컴파일된 바이너리에서는 이전 패키지 경로를 찾게 되어 ClassNotFoundException이 발생하게 됩니다.

많은 팀에서 사용하고 있을 경우 모듈을 참조하고 있다면 코드를 모두 업데이트해야 합니다 

 

➕ 공통으로 사용되는 기능을 모듈로 추출하는 것으로 많은 서비스에서 참조할 가능성이 있습니다. 변경의 여파가 넓음을 의미합니다. 따라서, 한 번 만들 때 제대로 만들 필요가 있습니다.