Notice
Recent Posts
Recent Comments
Link
반응형
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

coding etude

[Architecture] Clean Architecture의 이해(2) 본문

Flutter(Dart)

[Architecture] Clean Architecture의 이해(2)

코코리니 2025. 7. 31. 12:11
반응형

이번 포스팅에서는 Clean Architecture의 이해(1)의 내용을 바탕으로 예제를 보면서 확인해 보자.

사실 의존성의 흐름순으로 예제를 작성하는게 좋지만,

오늘은 조금 쉽게 이해를 돕기위해 앱의 로직순으로 예제를 만들어 보고자 한다.

(모든 예제는 flutter / dart 을 사용 합니다)

 

Presentation(UI)

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch<CounterProvider>().counter;

    return Scaffold(
      appBar: AppBar(title: Text('Clean Architecture Counter')),
      body: Center(child: Text('Count: $counter')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterProvider>().increment(), // provider에 이벤트 실행
        child: Icon(Icons.add),
      ),
    );
  }
}

provider

class CounterProvider extends ChangeNotifier {
  final IncrementCounter incrementCounter;

  Counter _counter = Counter(0);
  Counter get counter => _counter;

  CounterProvider({required this.incrementCounter}); // 의존성 주입(DI)(constructor)

  Future<void> increment() async {
    _counter = await incrementCounter(_counter); // 주입된 의존성 사용
    notifyListeners(); // 값이 변경됨을 알림
  }
}

Domain

use case

class IncrementCounter { // Use case
  final CounterRepository repository;

  IncrementCounter(this.repository); // 의존성 주입(DI)(constructor)

  Future<Counter> call(Counter counter) {// entity를 사용해 타입선언(검증)
    return repository.increment(counter); // repository로 data영역에 이벤트 요청
  }
}

domain pository

abstract class CounterRepository {
  Future<Counter> increment(Counter counter);
}

// 단순 interface 역할만을 담당

entity

class Counter {
  int value; // 단순하게 데이터의 검증 역할

  Counter(this.value);
}

// 왜 검증인가? 
// type 언어에서 Type 선언 했을 때, type 이 맞지 않으면 type 에러를 유발하기 때문에
// type 선언 만으로도 data의 검증 역할을 할 수 있음.

 

Data

repository

class CounterRepositoryImpl implements CounterRepository { // domain의 repo를 실제로 구현
DataSource remote;
CounterRepositoryImpl(this.remote); // 의존성 주입
  @override
  Future<Counter> increment(Counter counter) async {
    return await remote.increment(counter); // data source 호출
  }
}

data source

class CounterDataSourceImpl implements CounterDataSource {
  final NetDriver netDriver;
  ApplicationDataSourceImpl(this.netDriver); // newwork 의존성 주입
  
  @override
  Future<Counter> increment(Counter counter) async {
    String json = CountModel.toJson(counter); // model을 통해 json으로 변환.
    String url = 'api 주소';
    
    final respone = await netDriver.increment(url, json); // netDriver의 http 통신 
    return respone.fromJson(); // 서버에서 받은 데이터를 model을 통해 다시 int로 변환
  }
}

model

class CounterModel {
  int value;

  CounterModel(this.value);
  
  factory CounterModel.fromJson(Map<string, dynamic>) {
  	return CountModel(value: (json['counter'] as num?)?.toInt())
  } // json을 CounterModel로 변환
  
  Map<String, dynamic> toJson(this) {
  	return {'value': this.value};
  }// CounterModel을 json으로 변환.
}

 

마무리

지금까지 clean architecture의 간단한 로직을 예제로 만들어 보았다.

정말 간단한 예제 이지만, 이 예제 속에서 데이터의 흐름과 의존성이 어떤식으로 주입되는지를 찾을 수 있다면 큰 틀을 이해 안것이라고 생각한다. 

여기서 세부적으로 알아야 할 것은 use case이다. 

use case는 단순 거처가는 통로가 아니라 데이터를 가공하는 역할을 한다는 것이다.

그렇기 때문에 데이터를 가공하거나 새로 만들어야 한다면 use case 내에서 이루어 져야하고,

그 외 영역에서(UI, Data)에서는 데이터를 가공 하면 안된다는 점을 유의 해야 한다.

물론, 불가능하게 막아 놓은것은 아니지만, 다른 영역에서의 작업은 코드의 유지보수에 어려움을 야기하고 clean architecture 사용 장점을 퇴색 시키기 때문에 철저하게 지키는게 좋다.

이 포스팅은 여기서 마무리 하고 다음 포스팅은 DI(dependency injection) 의존성 주입에 대해서 간략하게 포스팅 해볼까 한다.

 

끝.

반응형