모듈화 아키텍처를 적용해 재사용성 높히기

최근 챗봇을 구현하는 과정에서 단일 프로젝트 내 클린아키텍처만 적용해 구현 하는데, 진행을 하다보니 구현 기능들이 추가되었고 점점 크기가 커져 나가는 것을 느꼈다. (다른 큰 프로젝트 비해서는 아주 작은 편이지만..)

 

모듈화에 관련한 스터디를 진행하게 되면서 이참에 기능 단위로 로직을 모듈화해 다른 프로젝트에서도 재사용성을 높히는 방법을 한번 적용해보려고 한다.

 

모듈화 방향성?

모듈화를 진행하면서 느끼는 건 어떤 방식으로 모듈화를 진행하는지에 대한 정답은 딱 정해져 있지 않은 것 같다. 그때 그 프로젝트, 개발 환경 등 각 요소에 맞춰 어떤 방향으로 모듈화를 진행할지, 유지 보수, 재사용성에 맞춰 진행을 방식도 있고, 협업 시 기능 구현을 나눠 충돌없이 개발이 진행 가능한 환경을 조성하던지.. 등등 아래의 목적에 맞게 진행하면 된다고 생각했다.

 

  1. 중복코드 방지 및 유지보수의 용이성
    유사 기능이 반복되고 중복된 로직이 여러 프로젝트에 사용된 경우, 한 곳을 수정하면 다른 프로젝트에는 변경 사항을 반영하지 못하는 경우 등
  2. 협업 효율 개선
    담당 기능별 협업을 통해 모듈화해 개발을 진행하면 각 기능을 독립된 모듈로 관리할 수 있어 코드 충돌이나 빌드 시간 증가 등 문제를 완화할 수 있다.
  3. 재사용성과 확장성 고려
    핵심 로직만 재사용하고 UI 관련 내용은 프로젝트별로 다를 수 있기 때문에 선택해 모듈화 진행 가능
    한번 개발해 여러 프로젝트에서 사용이 가능해지고 내가 선택해 모듈을 import해 사용할 수 있어 재사용성이 크게 높아진다.

나는 협업의 측면에서 생각하는 것 보다 기능 별로 로직을 세분화해 모듈화해두면 추후 다른 개발이 들어갈때 재사용성이 높게 구현할 수 있어 UI를 포함하지 않는 기능 단위로 로직을 모듈화 하는 방식을 선택했다!

 

몇가지 기능 별로 나눠 모듈화를 진행했지만 대표적으로 챗봇 로직 모듈화를 통해 어떤 방식으로 진행했는지 알아보면

Chatbot Package를 생성해 해당 패키지 안에 Domain과 Infra로 나눠 챗봇 기능을 할 수 있는 로직을 분류해 넣어놨다.

 

의존성 선언

챗봇 로직에는 외부 라이브러리를 사용 하기 때문에 Moya, Open AI 등등 해당 내용의 의존성을 명시해주어야한다.

import PackageDescription

let package = Package(
    name: "Chatbot",
    platforms: [
        .iOS(.v17)
    ],
    products: [
        .library(
            name: "Chatbot",
            targets: ["Chatbot"]),
    ],
    dependencies: [
        .package(url: "<https://github.com/MacPaw/OpenAI.git>", from: "0.3.6"),
        .package(url: "<https://github.com/Moya/Moya.git>", from: "15.0.3"),
        .package(url: "<https://github.com/firebase/firebase-ios-sdk.git>", from: "11.9.0")
    ],
    targets: [
        .target(
            name: "Chatbot",
            dependencies: [
                .product(name: "OpenAI", package: "OpenAI"),
                .product(name: "Moya", package: "Moya"),
                .product(name: "FirebaseDatabase", package: "firebase-ios-sdk")
            ],
            path: "Sources/Chatbot"
        ),
    ]
)

그 이유는 Package.swift 파일의 dependencies 섹션에서 외부 패키지를 가져오고 빌드 및 링크할 수 있도록 관리해 줘야 하기 때문이다.

상기 코드 처럼 디펜던시를 명시해 작성하면 URL을 통해 소스코드를 다운받고 프로젝트 빌드 시 필요한 타겟을 생성해 준다.

 

  • .product(name: "OpenAI", package: "OpenAI"):
    “OpenAI”라는 패키지 안의 “OpenAI”라는 라이브러리(제품)를 사용하겠다는 의미
  • .product(name: "Moya", package: "Moya"):
    “Moya” 패키지 내의 “Moya” 라이브러리를 링크
  • .product(name: "FirebaseDatabase", package: "firebase-ios-sdk"):
    “firebase-ios-sdk” 패키지 중 “FirebaseDatabase” 라이브러리를 사용

이렇게 의존성을 관리하는 이유는 여러가지가 있는 것 같다.

  1. 소스코드에서 해당 라이브러리 기능을 사용하기 때문
  2. 자동 의존성 관리 편리
  3. 모듈 재사용 및 명확한 구조

결국 이 Chatbot 모듈이 Moya, Firebase, OpenAI 라이브러리를 사용한다! 라는 사실을 명시해 프로젝트가 원활하게 빌드되고 이 모듈을 다른 프로젝트에서도 손쉽게 재사용할 수 있게 되기 때문!! 이라고 이해하면 될 것 같다.

 

모듈 구성

그 다음 이제 패키지 구성을 살펴보면 챗봇 로직은 추후 다른 프로젝트에서도 사용 가능하도록 모듈화를 해두기로 했기 때문에 UI 관련 내용은 추가하지 않고 핵심 도메인과 로직만 담아 구성하였다.

 

먼저 도메인의 Entity를 생성해 기존에 사용하던 데이터 모델을 넣어주고 UseCase Protocol , Repository Protocol 등 Domain을 구성해 준 뒤 Infra에 UseCaseImpl , RepositoryImpl과 API 와 연결하는 Network를 배치했다. 모듈 내부의 코드 구성은 기존에 사용했던 클린아키텍처 구조와 동일하게 구성하면 되는 것 같다..

 

이렇게 로직을 구성하면서 접근제어자에 대한 이해가 필요하다는걸 깨달았다. 이전에 접근제어자에 대한 내용을 이해는 하고 있었지만 직접적으로 다른 프로젝트 파일 공유 등 그 역할과 범위를 명확하게 피부로 느끼기 어려운 점이 있었다.

이 모듈 구성을 통해 public으로 선언해야하는 점 기본 internal 접근제어자를 사용했을때 생기는 오류 등등 마주하며 이래서 public을 선언하고 내부 프로젝트에서는 private를 사용하고 이런 내용을 이전보다 더 심도있게 알게 된 것 같다.


이런식으로 기능별로 나눠 모듈을 만들어두면 사용할 프로젝트 내 하단의 사진과 같이 그냥 import해서 내부 내용들을 사용하면 된다.

 

이전까지는 그냥 가져와서 import해서 사용한다 정도만 알았다면 이런식으로 패키지를 구성해서 직접 만들어 사용해보니 우리가 기존에 사용했던 라이브러리 등 어떤 방식으로 구성되어 있는지 더 이해할 수 있는 계기가 된 것 같다..

그전까지는 그냥 당연하게 크게 이유를 생각하지 않고 사용했던 내용들이 많은 것 같다. 하나씩 그 방식을 이해하고 사용해봐야지 ㅠㅠ 아직 갈길이 멀다 오늘은 여기까지!!!