Modular Architecture 구성 및 적용 방법 (Scaffold, Template)

Jayven·2024년 3월 20일
1

모듈화 With Tuist

목록 보기
3/4
post-thumbnail
post-custom-banner

안녕하세요 제이븐입니다 😁😁

저번 포스팅에이어서 이번에는 Modular Architecture를 프로젝트에 적용하는 방법과 Module을 쉽게 생성하게 도와주는 Scaffold, Template에 대해서 알아보도록 하겠습니다.

우선 해당 과정을 프로젝트에 적용하기 위해서 작은 앱을 만들면서 설명해보겠습니다! 깃허브 레포를 참고 하면서 글을 보면 더 도움 될 것 같습니다
만다르트 앱 깃허브


모듈 구성


우선 모듈을 만들기 전에 어떻게 모듈을 구성할지 간단하게 설계해보고 가겠습니다

클린아키텍처 구조로 만들었습니다


모듈 생성


MicroFeature Module

MicroFeature를 생성하려면 총 5개의 모듈을 생성해야됩니다 Example(Demo), Test, Sources, Testing, Interface

우선 저는 5개가 아닌 4개로 만들어 보겠습니다 SwiftUI로 프로젝트를 작업할 예정이기에 Preview를 보기 위해서 Mock데이터가 Sources에 존재하는게 좋을 거라고 생각되어서 이렇게 진행하게 되었습니다.

아래와 같이 Sources와 Testing을 합쳐서 하나의 Sources 모듈로 가져가겠습니다.


모듈 생성을 도와주는 Template

Manifests/Tuist에 Templates 폴더를 생성해줍니다

Templates는 Tuist에서 정해놓은 이름이기 때문에 다른 폴더명이 다르면 Templates을 인식하지 못합니다

템플릿을 만드는 규칙은 Templates/{templates 이름 폴더}/{templates 이름}.swift 아래와 같이 templates 파일과 폴더명이 일치해야됩니다.


module.swift + Attribute

module.swift 파일에 아래와 같이 작성해줍니다

Template.Attribute 는 scaffold로 템플릿을 불러올때 tuist scaffold module --name {값} 에 --attribute명 으로 값을 전달 받을 수 있게 합니다.

Attribute는 필수, 옵셔널 두가지를 선택할 수 있습니다. 저는 항상 값을 입력하려고 해서 .required를 사용하겠습니다

그리고 items는 템플릿을 사용해서 자동으로 생성할 파일의 배열입니다

  • path: 템플릿으로 폴더 + 파일을 생성할 경로
  • templatePath: path로 생성한 파일의 내용을 넣기 위한 템플릿의 위치를 넣으면 됩니다.
import ProjectDescription

// command line 입력
// ex) tuist scaffold module --name "모듈명"

let nameAttribute: Template.Attribute = .required("name")

let template = Template(
    description: "Make MicroFeature Module",
    attributes: [nameAttribute],
    items: [
        // MARK: - Project.swift
        .file(path: "Projects/Features/\(nameAttribute)/Project.swift",
              templatePath: "stencil/project.stencil"),
        
        // MARK: - Sources
        .file(path: "Projects/Features/\(nameAttribute)/Sources/empty.swift",
              templatePath: "stencil/emptyFile.stencil"),
        
        // MARK: - Example(Demo)
        .file(path: "Projects/Features/\(nameAttribute)/Demo/Sources/AppDelegate.swift",
              templatePath: "stencil/appDelegate.stencil"),
        .file(path: "Projects/Features/\(nameAttribute)/Demo/Sources/SceneDelegate.swift",
              templatePath: "stencil/sceneDelegate.stencil"),
        
        // MARK: - Tests
        .file(path: "Projects/Features/\(nameAttribute)/Tests/Sources/empty.swift",
              templatePath: "stencil/emptyFile.stencil"),
        
        // MARK: - Interface
        .file(path: "Projects/Features/\(nameAttribute)/Interface/Sources/empty.swift",
              templatePath: "stencil/emptyFile.stencil")
    ]
)

Stencil

Stencil은 .swift 파일을 템플릿화 하는 파일입니다.
위에 project.stencil을 예시로 stencil을 만들어 보겠습니다.

아래 {{name}}은 위에서 선언한 Attribute가 들어가게 됩니다.

import ProjectDescription
import ProjectDescriptionHelpers
import UtilityPlugin

let project = Project.makeModule(
    name: "{{ name }}",
    target: Set(FeatureTarget.microFeature),
    internalDependencies: [
        
    ]
)

Template의 전체 폴더 구조


Makefile + Scaffold

자 이제 4개의 모듈을 한번에 생성하고 그래프를 찍어보겠습니다

그 전에 tuist scaffold module --name MainFeature 이런 식으로 매번 Scaffold를 생성하면 조금 번거로울 수 있으니 MakeFile을 같이 사용해서 조금 더 쉽게 생성해보겠습니다

프로젝트 Root에 Makefile을 생성해 줍니다.

touch Makefile

그리고 Makefile안에 아래와 같이 넣어줍니다.
이렇게 Makefile을 사용해서 make module name=MainFeature 로 모듈을 생성할 수 있습니다

바로 실행해서 결과를 확인해보도록 하겠습니다.

.PHONY: generate

# MicroFeature 모듈 생성
module:
	@tuist scaffold module --name ${name}

의도한 대로 Demo, Interface, Source, Tests 4개의 목록이 생겼습니다 🎉🎉🎉


추가적으로 rootFeature의 의존성을 MainFeature에 연결 시켜주고 graph를 찍어서 결과를 보겠습니다

아래와 같이 MainFeature가 생성 되었다면 앞으로 MicroFeature 모듈은 make module name=으로 쉽게 생성이 가능합니다!


Domain, Core 등의 Module 생성


Domain, Core, Shared, DesignKit, NetworkKit, ThirdParyLib 모듈을 생성하고 위에서 말한대로 모듈을 연결해주면 최종적으로 위에 그려놨던 그래프처럼 완성됩니다.


ThirdPartyLib

ThirdPartyLib는 모든 라이브러리를 가지고 있는 최하위 모듈입니다 예시로 Moya 라이브러리를 설치하고 연결해보겠습니다

Tuist 폴더 안에 Package.swift 파일을 생성해줍니다.

이전에는 Dependencies.swift 였는데 최신 버전에서는 Package.swift로 변경되었습니다 (사용 버전 4.5.1)

우선 어떻게 바뀌었는지 코드로 비교해보겠습니다


Before (Dependencies.swift)

SPM Dependency를 만들어서 Depencencies의 생성자 swiftPackageManager에 넣어줍니다

let spm = SwiftPackageManagerDependencies([
    .remote(url: "https://github.com/Moya/Moya.git", requirement: .exact("15.0.3"))
], baseSettings: Settings.settings(
    configurations: XCConfig.framework
))


let dependencies = Dependencies(
    swiftPackageManager: spm,
    platforms: [.iOS]
)

After (Package.swift)

아래는 위에와 같은 package 추가 과정이고 TUIST 전처리 안에 있는 부분는 PackageSettings을 사용하여 종속성에 대한 커스텀 매핑을 추가하는 구문입니다

// swift-tools-version:5.9
import PackageDescription

#if TUIST
    import ProjectDescription
    import ProjectDescriptionHelpers
    import UtilityPlugin


    let packageSettings = PackageSettings(
        productTypes: [
            "Moya": .framework,
        ], baseSettings: Settings.settings(configurations: XCConfig.framework)
        
    )
#endif

let package = Package(
    name: "EggtartPackage",
    dependencies: [
        .package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "15.0.3"))
    ]
)

설치는 이전 버전에서는 tuist fetch 를 사용했지만 현재는 tuist install 로 바뀌었습니다.


마무리


Plugin에 정의해 놓은 것도 포스팅에 넣고 싶었지만 코드가 조금 많아서 따로 추가하지는 못했습니다

그래서 코드를 따라서 치다 보면 안되는 부분이 많을 것 입니다 ㅠㅠㅠ

Plugin에 대한 부분은 나중에 추가적으로 보충을하거나 다시 포스팅 할 생각입니다

질문과 피드백은 언제나 감사합니다!

profile
iOS 개발자
post-custom-banner

0개의 댓글