Tech Dive

소상공인 혁신 플랫폼 ‘사장이지’ 출시 App편 #1. Modular Architecture

들어가며


안녕하세요, KT Experience Engineering 본부에서 모바일 앱 개발자로 근무하고 있는 최유태입니다. 


소상공인 혁신 플랫폼 '사장이지' 앱을 개발하면서 모바일 아키텍처 관점에서 고민하고 여러 문제를 겪었던 경험을 공유하고자 합니다.



먼저, 사장이지 앱이란?


KT에서 소상공인을 위한 4대 서비스(통화비서, 기가아이즈, 로보케어, 하이오더)와 타사 제휴 서비스(AI 매장음악 등)를 통합한 소상공인 사업성장 풀케어 플랫폼입니다.


01.jpg



초기 아키텍처 검토


"어떻게 하면 대규모(4개의 앱+@) 프로젝트를 효율적으로 관리하면서 빠르게 개발할 수 있을까?"


모바일 Clean Architecture 기반의 도메인 중심 아키텍처를 논쟁 없이 채택했지만, High-Level 관점에서 Layer First vs Feature First에 대한 여러 논의가 있었습니다.


Layer First: 레이어(presentation/domain/data) 단위로 나누고, 그 안에 기능들을 배치하는 방식

TypeScript
/lib
  /domain
    /featureA
    /featureB
  /presentation
    /featureA
    /featureB
1
2
3
4
5
6
7


Feature First
: 기능 단위로 나누고, 그 안에 관련 레이어를 모아두는 방식

TypeScript
lib/features
  /featureA
    /data, /domain, /presentation
  /featureB
    /data, /domain, /presentation
12345


장단점 비교



Layer First
Feature First
장점
  • 계층별 역할이 명확함
    - 전반적인 프로젝트 구조가 명확함
  • 초기 비용이 Feature First에 비해 적음
  • 규모가 작은 프로젝트에 유리함
  • 공통 코드로 분리하기 편리
  • 독립적인 구조
        - 기능 단위 테스트 용이
        - 기능 단위 앱 실행 가능
        - Modular Architecture 유리
  • 기능 단위로 작업, 테스트, 삭제가 직관적
  • 대형 프로젝트에 유리함
단점
  • 독립적인 기능만 검증 또는 기능 테스트하기 어려움
  • 초기 비용이 큼
  • 공통 코드 사용하기 어려움
        - 공통으로 사용될 경우, 공통 모듈로 분리

"초기 비용이 크더라도 구조상 이점이 많은 Feature First가 낫지 않을까?"


"현 상황에서 정말로 Feature First 유리할까? 오히려 보일러플레이트 코드가 더 많이 발생하지 않을까?"


검토 당시 Feature First의 구조적 이점은 충분히 인지했지만, 개발 일정상 초기 비용을 무시할 수 없었습니다. 엔지니어 관점에서 기술과 아키텍처링이 정말 중요하지만, 신규 프로젝트이기에 비즈니스 관점이 더 중요하다고 판단했습니다. 


따라서 Time To Market을 위해 Layer First를 채택했습니다.


기존 방식 그대로 적용하기보다는 Layer First의 단점을 보완하고, 특정 모듈은 독립적으로 Feature First 형태로도 지원할 수 있도록 팀 내 Ground Rule 정하고 여러 장치를 마련했습니다.


  1. Domain, Data 레이어를 모듈로 관리하고 내부적으로는 폴더 단위로 Feature 구분

  2. 공통 기능이 아닌 경우에는 Feature간 의존성(참조) X

  3. 규모가 큰 Feature는 각각 ‘모듈’ 단위로 관리 (기가아이즈, 로봇, 통화비서 등)

  4. 여러 Feature에서 공통으로 사용하는 것들은 common, util 모듈로 분리

  5. 모든 공통 로직을 하나의 common 모듈로 관리하지 않고, 규모가 있는 기능들은 별도 모듈로 관리

    1. analytics, remote_config, design_system 등


02.jpg

< High-level 관점, Layer-First 아키텍처 다이어그램 >



출시 이후 고도화 업데이트 과정에서 Layer First의 한계 경험 


1. 높은 결합도(Coupling)

다른 Feature에 변경이 생기더라도 data/domain 모듈 변경으로 인해 사실상 영향이 없는 기능이더라도 Side-effect 확인이 필요했습니다.


또한, 공통 도메인(auth, store 등)에 변경이 생기는 경우에도 자체적으로 보장이 어렵기 때문에 전체 Feature에 미치는 영향이 더 컸습니다.


2. Feature 단위의 독립적인 example 앱

개발 생산성을 위해 기능 단위로 병렬 형태로 개발 진행하였고 Integration 전까지는 각 모듈의 example 앱을 활용했습니다.


Layer First에서도 example 앱을 활용할 수는 있지만 불필요한 domain/data 소스코드까지 포함되기 때문에 항상 호환성을 고려해야 단점을 경험했습니다.


3. 신규 서비스(통화비서/AI 전화)에서 새로운 OS 플랫폼 web 지원

통화비서를 사장이지 App에 추가하는 작업뿐만 아니라, 상담사분들이 원활하게 고객 대응을 할 수 있도록 Back-office 형태의 Web 플랫폼도 지원이 필요했습니다.


즉, 하나의 모듈로 2개의 애플리케이션(사장이지 앱 / 상담사 웹)을 지원해야 하는 상황이었습니다.


Flutter Web 지원을 검토해보니, Web 플랫폼에 대응하지 않는 플러그인과 로직 등으로 인해 컴파일 자체가 되지 않았습니다.


이 과정에서 특히, 신규 서비스에 불필요한 코드들이 Layer First 구조이기 때문에 참조되었고 이 단점을 가장 크게 경험했습니다.


4. Minor한 단점들

이외에도 아래 단점들을 경험하였습니다.


  • 도메인간의 의존 관계를 도출하기 어려움
  • 단위테스트가 결국 통합 테스트가 되는 현상
  • 폴더로 Depth 구분하더라도 파일명/클래스명 Unique 보장하기 위해 Prefix 등 필요 



한계를 극복하기 위해 Feature First 도입


신규 서비스를 추가할 때는 기존 구조와 완전히 분리된 독립적인 모듈 형태(Feature First)로 구성하여 하나의 모듈로 2개의 애플리케이션을 지원했습니다.

기존 도메인의 기능이 필요한 경우, 직접 참조하지 않고 외부에서 의존성을 주입받는 방식(Dependency Injection)으로 개발하여 도메인 간 결합도를 낮추고 확장성과 안정성을 확보했습니다.

03.jpg
< High-level 관점, Feature-First 아키텍처 다이어그램 >

오히려 생산성(Productivity)이 높았다?

Feature-First의 가장 큰 단점인 개발 비용 이슈가 실제로 경험해보니 궁극적으로는 오히려 개발 생산성에 도움이 되었습니다.


빠른 Feasibility:

개발 초기 단계부터 다른 도메인으로부터 완전히 분리된 모듈을 활용해 Flutter Web 빌드를 직접 시도해봄으로써, Web 플랫폼 지원 가능 여부를 사전에 명확히 검증할 수 있었습니다.


이러한 사전 검증 과정은 프로젝트의 핵심 의사결정에 매우 중요한 역할을 했습니다.


적극적인 example 앱 활용:

독립 모듈로 다른 도메인과의 디펜던시 없이 example 앱 개발


  1. 공통(auth, store 등) 연동 없이 UI/UX 개발

  2. 화면 Flow와 상관없이 병렬적으로 개발 가능

  3. 암호화, 플레이어 등 PoC가 필요한 과제도 별도 의존성 없이 빠르게 검증



앞으로의 아키텍처 방향성


1. 공통 Domain(auth, store, user 등)부터 리팩토링

여러 도메인과의 디펜던시가 존재하는 공통 도메인을 먼저 리팩토링하여 전환함으로써, 기존 Feature와 새로운 Feature 모두 리팩토링 및 개발이 편리하도록 할 예정입니다.


2. 모듈 단위 기준의 아키텍처 검토

모든 도메인이 모듈로 구성된다면, High-level 관점으로 모듈 단위 기준으로의 아키텍처를 검토할 예정입니다.


  1. High-level 관점으로 Layer 구성 및 단방향 참조

    1. Feature Layer / Common Layer

  2. Circular Dependency 방지

    1. A모듈 ←→ B 모듈 상호 참조

  3. Feature 간의 Dependency 제거


또한, Rule들을 시스템적으로 Detecting 하기 위해서 pubspec.yaml 기준으로 CI, git hook 등 활용할 예정입니다.


3. Feature First의 단점 보완

가장 큰 단점인 보일러플레이트 코드를 극복하기 위해 팀에서 여러 노력을 하고 있습니다. 


AI Agent

  • AI Rules : Flutter 에서 제공하는 Rule(AI rules for Flutter and Dart)기반에 프로젝트 아키텍처 + 팀 내 Coding Convention 등을 Custom

    • 신규 서비스(통화비서) 모듈을 Best Practice 지정

  • Clean Architecture에서의 Use Case, Data Source, Repository 등 기반 생성에 활용

  • 안정성 확보를 위해 Test Code 작성에 기여


Template Generator

mason | Dart package 사용한 Template 방식도 활용

TypeScript
​bricks/feature
  /lib
    /data
      /repository
        {{name.snakeCase()}}_repository_impl.dart
      /data_source
        {{name.snakeCase()}}_data_source.dart
      data.dart
    /domain
      /repository
        {{name.snakeCase()}}_repository.dart
      /use_case
        {{name.snakeCase()}}_use_case.dart
      domain.dart
    /presentation
      /bloc
      /widget
    {{name.snakeCase()}}.dart
  pubspec.yaml
12345678910111213141516171819

해당 Template이 결국 Best Practice가 될 예정입니다.


Code Snippet

stl, stf 처럼 팀 내 자주 사용하는 코드 블록을 관리할 예정입니다.

  • freezed, usecase, …

Python
​// freezed
​
import 'package:json_annotation/json_annotation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
​
part '$filename$.freezed.dart';
​
part '$filename$.g.dart';

​@freezed
class $model$ with _$$$model$ {
  factory $model$({
    $END$
  }) = _$model$;
​
  factory $model$.fromJson(Map<String, dynamic> json) => _$$$model$FromJson(json);
}
1234567891011121314151617


여기까지 Modular Architecture 주제의 아티클을 마무리하고, 다음 글에서 Feature First 형태로 개발된 모듈을 활용한 Back Office 통화비서 상담사 웹(Web)을 지원하면서 경험했던 Flutter Web 이슈 트러블슈팅을 공유해드리겠습니다. 




🤗 함께한 사람들: P-App개발팀 

모바일 앱 개발자로 구성되어 있으며 특정 플랫폼 제약 없이 iOS, Android, Flutter, Kotlin Multiplatform 등 내재화 개발 가능하며 프로젝트 특성을 고려하여 최적화된 플랫폼을 채택하고 있습니다.

최유태

KT에서 Software Engineer(Mobile)로 근무하고 있으며, 어제보다 더 나은 엔지니어가 되기 위해 항상 노력하고 있습니다.

이전 글