Flutter에서 REST API와 소켓을 구조적으로 관리하는 방법

Peter SHIN·2025년 4월 30일

Flutter에서 앱 구조를 설계할 때, 특히 REST API소켓 통신을 다루게 되면
Service와 Repository를 어떻게 나누는 게 맞을까?
이 질문 하나만 명확히 정리돼도 실무 구조 설계가 훨씬 쉬워진다.


✅ 핵심 요약

계층역할설명
Model데이터 구조fromJson, toJson 포함
RepositoryAPI 통신 담당API 요청만 수행 (getUser(), postComment() 등)
Service비즈니스 로직 조합여러 Repository를 묶거나 상태 흐름 관리
ViewModel상태 관리 전담Service를 호출하고 상태 업데이트
ViewUI 구성ViewModel의 상태만 사용

🔁 구조 흐름 요약

📡 REST API

[View] → [ViewModel] → [Service] → [Repository] → [서버 API]

소켓 통신

[View] → [ViewModel] → [SocketService] → [Socket Server]
                            ↑ 계속 연결 유지
lib/
├── models/
│   └── user.dart
├── repositories/
│   └── user_repository.dart      # API 요청만 담당
├── services/
│   ├── auth_service.dart         # 로그인/회원가입 로직
│   └── socket_service.dart       # 소켓 연결, 메시지 송수신
├── viewmodels/
│   └── auth_view_model.dart      # 상태 + 로딩 처리
└── main.dart
Repository: API 요청 전담
class UserRepository {
  final Dio dio;

  Future<Map<String, dynamic>> fetchUser(String id) async {
    final res = await dio.get('/users/$id');
    return res.data;
  }
}

🔸 Service: 비즈니스 로직 조합

class AuthService {
  final UserRepository userRepository;

  AuthService(this.userRepository);

  Future<User> login(String email, String pw) async {
    final data = await userRepository.fetchUser(email);
    return User.fromJson(data);
  }
}

🔸 ViewModel: 상태 + 로직 관리

class AuthViewModel extends StateNotifier<AsyncValue<User?>> {
  final AuthService authService;

  AuthViewModel(this.authService) : super(const AsyncValue.data(null));

  Future<void> login(String email, String pw) async {
    state = const AsyncLoading();
    try {
      final user = await authService.login(email, pw);
      state = AsyncData(user);
    } catch (e, st) {
      state = AsyncError(e, st);
    }
  }
}

📞 소켓은 어디에 둘까?

services/socket_service.dart로 통일
왜? 소켓은 요청-응답이 아닌 "지속적 연결 + 이벤트 통신"이기 때문

import 'package:socket_io_client/socket_io_client.dart' as IO;

class SocketService {
  late IO.Socket socket;

  void connect() {
    socket = IO.io('https://server.com', {
      'transports': ['websocket'],
    });

    socket.connect();

    socket.on('message', (data) {
      print('받은 메시지: $data');
    });
  }

  void sendMessage(String msg) {
    socket.emit('message', msg);
  }

  void disconnect() {
    socket.disconnect();
  }
}

❓ Service만 쓰고 Repository는 생략 가능할까?

가능하다!
특히 작은 앱, MVP, 단순 로직일 경우 Service에서 API 호출도 직접 해도 무방하다.

class AuthService {
  Future<User> login(String email, String pw) async {
    final res = await Dio().post('/login', data: {
      'email': email,
      'password': pw,
    });
    return User.fromJson(res.data);
  }
}

🧾 정리 요약

상황추천 구조
MVP, 개인 앱ViewModel → Service 구조로 간단하게
유지보수, 협업ViewModel → Service → Repository 구조로 확장
실시간 소켓 통신SocketService 따로 관리 (service 계층에 둠)
통신만 필요할 경우Repository 단독 사용도 가능
profile
플러터,자바,c언어

0개의 댓글