Flutter, 잘 키운 FireStore Mixin 모듈, 열 백엔드 개발자 안부럽다.

Ximya·2023년 3월 27일
3

Plotz 개발일지

목록 보기
3/12
post-thumbnail

해당 포스팅은 유튜브 영화&드라마 리뷰 영상 큐레이션 플랫폼 Plotz를 개발하면서 도입된 기술 및 방법론에 대한 내용을 다루고 있습니다.
다운로드 링크 : 앱스토어 / 플레이스토어

클라이언트 개발자가 백엔드 개발자의 도움 없이 프로젝트를 구현할 때 가장 만만한 선택지 Firebase가 아닐까 싶습니다.
저 또한 '순삭'서비스를 구현하면서 대부분의 서버 통신 로직에 firestorefirebase realtime database를 적극적으로 도입했고요.

Firebase의 firestore을 사용하면서 편리한 부분도 정말 많았지만, 클라이언트에서 일일이 쿼리문을 작성해야 된다는 점이 귀찮았습니다. 백엔드 개발자가 있었다면 간단하게 그들이 제공해주는 명세에 맞게 필요한 값들을 전달하면 될 텐데 말이죠.

좀 더 간단하고 편리하게

그래서 저의 이런 귀찮음을 덜기 위해 FireStore 네트워킹 통신을 쉽게 도와주는 모듈을 만들기로 했습니다.
백엔드 개발자가 제공해주는 API처럼 명세에 맞게 필요한 값들만 전달하면 네트워킹 기능을 수행하는 모듈을요.

FireStore 네트워킹 모듈... 근데 Mixin을 곁들인

앞으로 소개해드릴 FireStore 네트워크 통신 모듈은 3가지 콘셉트를 가지고 있습니다.

  1. 코드의 재사용성
    Firestore 네트워크를 기능을 구현하는데 데 필요한 코드를 한 곳에서 모아서 관리하여 코드의 재사용성을 높임.
  2. 쿼리 작성 간소화
    FireStore 쿼리문에 익숙하지 않은 작업자라도 쉽게 필요한 네트워킹을 수행할 수 있도록 도와줌
  3. 유지 보수에 용이
    모듈화된 코드는 유지 보수하기 쉬우며, 수정 및 업데이트가 필요할 때 해당 모듈을 수정하면 됨. 이렇게 하면 모듈 외부에서 영향을 받지 않으므로 코드 유지 보수성이 향상됨.

그리고 이런 콘셉을 가지 모듈을 dart에서 제공하는 mixin 키워드를 기반으로 합니다.

자, 그럼 이제 mixin을 이용해서 어떻게 Firebase 네트워킹 모듈을 작성했는지, 그리고 mixin을 이용했을 때 가지는 장점들을 살펴보려고 합니다.

Mixin이 뭔데?

먼져 Flutter 공식 문서에 mixin에 대한 설명은 아래와 같습니다.

Mixins are a way of reusing a class’s code in multiple class hierarchies.

해석하자면 mixin은 다른 계층 구조에서 클래스의 코드를 재사용하는 방법이라고 정의되어 있네요.
여기서 말하는 다른 계층 구조는 뭘까요?

class Employee {
	void goToOffice() {...}
}

mixin DevSkills {
	void writeCode() {...}
    void unitTest() {...}
}

mixin DesignSkills {
	void makeProtoType() {...}
    void createLowFiDesign() {...}
}

class FlutterDeveloper extends Employee with DevSkills {}
class ProductDesigner extends Employee with DesignSkills {}

위 코드에서는 FlutterDeveloperProductDesigner클래스 모두 Employee 클래스를 상속받고 있습니다. 같은 계층의 클래스끼리 상속을 받고 있다고 볼 수 있죠.

하지만, with 키워드로 mixin이 되어 있는 DevSkillsDesignSkills는 클래스가 아니므로, 계층 구조에서 클래스와 다른 개념으로 분류됩니다. 즉 mixin은 클래스에서 코드를 재사용하기 위한 방법 중 하나이며, 기존 클래스의 기능을 확장하거나, 새로운 기능을 추가하기 위해 사용됩니다.

class IosDeveloper extends Employee with DevSkills {
	writeCode()
    unitTest()
}

예를 들어 IosDeveloper라는 클래스를 새로 만든다고 했을 때 기존에 구현한 DevSkills mixin 모듈을 include 시켜, IosDeveloper 클래스에서도 writeCode() unitTest() 메소드들을 사용할 수 있게 됩니다.

Mixin은 상속 없이 객체에 기능을 추가할 수 있도록 도와줌. (더 유연한 구조)


Mixin 기반의 리팩토링

자 그럼 예제 코드를 통해 mixin을 적극 활용해 봅시다.

class ContentApi {
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  Future<List<Content>> loadContents() async {
    final snapshot = await _db.collection('content').get();
    final docs = snapshot.docs;
    final result = docs.map((e) => Content.fromDoc(e)).toList();
    return result;
  }

  Future<List<Channel>> loadContentChannels() async {
    final snapshot = await _db.collection('channel').get();
    final docs = snapshot.docs;
    final result = docs.map((e) => Channel.fromDoc(e)).toList();
    return result;
  }

  Future<Content> loadContentById(String id) async {
    final snapshot = await _db.collection('content').doc(id).get();
    final result = Content.fromDoc(snapshot);
    return result;
  }

  Future<Channel> loadChannelById(String id) async {
    final snapshot = await _db.collection('channel').doc(id).get();
    final result = Channel.fromDoc(snapshot);
    return result;
  }
}

ContentApi 클래스에서는 FireStore로부터 콘텐츠 관련 데이터를 호출하는 역할을 담당하고 있습니다. 총 4개의 메소드를 관리하고 있는데 아래와 같이 2가지 형태로 구분됩니다.

  • 특정 Collection의 document 리스트 호출
  • 특정 Collection에서 특정 document 데이터 호출

그럼 이 부분을 따로 모듈화 할 수 있겠죠?

이제 FireStore호출 메소드를 따로 관리하는 Mixin 모듈을 만들어보겠습니다.

mixin FirestoreMixin {	
  // FireStore 인스턴스
  final FirebaseFirestore _db = FirebaseFirestore.instance;

  // collection에 속한 document리스트를 불러오는 메소드
  Future<List<DocumentSnapshot>> getDocs(String collectionName) async{
    final snapshot =await _db.collection(collectionName).get();
    return snapshot.docs;
  }

  // 특정 document를 id값을 통해 불러오는 메소드
  Future<DocumentSnapshot> getDocumentById(String collectionName, {required String documentId}) async{
    return _db.collection(collectionName).doc(documentId).get();
  }

Mixin 모듈에 FireStore 인스턴스를 해당 모듈에서 선언하고, 요구사항에 따라 DoucmentSnapshot 또는 List<DoucmentSnapshot> 타입의 데이터를 리턴하는 메소드를 정의합니다.

요구사항에 따라 메소드의 구성이 유동적으로 변경될 수 있습니다.

class ContentApi with FirestoreMixin {
  Future<List<Content>> loadContents() async {
    final snapshot = await getDocs('content');
    return snapshot.docs.map((e) => Content.fromDoc(e)).toList();
  }

  Future<List<Channel>> loadContentChannels() async {
    final snapshot = await getDocs('channel');
    return snapshot.docs.map((e) => Channel.fromDoc(e)).toList();
  }

  Future<Content> loadContentById(String id) async {
    final snapshot = await getDocumentById('content', documentId: id);
    return Content.fromDoc(snapshot);
  }

  Future<Channel> loadChannelById(String id) async {
    final snapshot = await getDocumentById('channel', documentId: id);
    return Channel.fromDoc(snapshot);
  }
}

마지막으로 FireStoreMixin 모듈을 with 키워드로 ContentApi에 연동하면 더 간단하고 직관적으로 FireStore 네트워킹 기능들을 사용할 수 있게 됩니다.
중복된 코드를 줄이고 재사용성을 훨씬 높인 구조라고 볼 수 있습니다. 가독성도 더 좋아졌네요.


FireStore Mixin 모듈의 이점

그럼 구체적으로 FireStoreMixin 모듈을 적용해서 얻는 이점들은 무엇이 있을까요? '순삭' 서비스를 개발하면서 느꼈던 점을 공유해보려고 합니다.

유지 보수에 용이

FireStore 네트워킹 메소드를 모듈화하면서 유지 보수하기 쉬운 구조가 됩니다. 수정 및 업데이트가 필요할 때 해당 Mixin 모듈만 변경하면 되기 때문이죠.

개발 속도 대폭 향상

사전에 복잡한 네트워킹 메소드들은 Mixin모듈에 정의했다면, 쿼리를 작성하는 부분이 간소화 되고, 결과적으로 개발 시간이 단축됩니다. '순삭'에는 복잡한 FireStore 페이징 API Call 로직이 많이 사용되는데 잘 정리된 페이징 기능을 Mixin에 정의하고 여러곳에서 간편하게 사용했었습니다.

특히 순삭 어드민 어플리케이션을 구현할 때 굉장히 득을 크게 봤습니다. 해당 어드민 페이지에서도 동일하게 FireStore 네트워킹을 로직을 요구하는 기능이 많다보니, 기존에 만들었던 FireStoreMixin 모듈을 활용할 수 있는 부분이 많았거든요.

그래서 FireSotreMixin모듈을 하나 잘 만들어 놓은다면, FireStore을 사용하는 다른 프로젝트에서도 범용적으로 편리하게 사용할 수 있겠다는 생각을 했습니다.

확장성

Flutter에서는 기본적으로 다중상속이 불가능합니다. 하지만 Mixin은 상속 없이 객체에 여러 기능을 가지고 있는 모듈을 추가해줄 수 있기 때문에 클래스의 확장성을 높여줄 수 있습니다.
예를들어 앞서 예제로 다루었던 ContentApi에서 Firebase의 Realtime Database 네트워킹 로직이 추가된다고 했을 때, 똑같이 Realtime Database Mixin 모듈을 만들고 ContentApi 클래스에 추가하면 됩니다.
결과적으로 기존의 DB환경과 다른 별도의 DB구성이 접목이 되어야 할 때, 더 확장성 있는 구조로 쉽게 대응할 수 있게 됩니다.

class ContentApi with FirestoreMixin, FireRealtimeDBMixin {
	Future<void> realTimeDBIntent() {}
	...
}

NOTE: Mixin은 다중상속과 유사한 효과를 낼 수 있지만, 엄밀한 의미에서는 다중상속이 아닙니다.


마무리하면서

이번 포스팅에서는 Mixin의 개념과 Mixin을 접목한 FireStore네트워크 모듈에 대해 소개해 드렸습니다. FireStore 네트워크 통신 모듈 말고도 여러 부분에서 Mixin 패턴이 적용될 수 있는데요. Mixin이 가지는 이점을 잘 활용하신다면 클래스를 좀 더 유연하고 다채롭게 구성하실 수 있을 것 같습니다 😀

profile
https://medium.com/@ximya

0개의 댓글