整洁架构(Clean Architecture)由罗伯特·C·马丁(Bob大叔)提出,是一套分层软件设计范式,核心作用是将业务规则与界面框架、UI层、外部第三方依赖完全解耦。在Flutter开发中,控件、状态管理、平台插件极易堆砌在同一文件内,代码混乱;而整洁架构能够梳理代码层级、提升可测试性,并保障项目长期可维护。本文基于企业级真实开发案例,搭配Dart实操代码,完整讲解Flutter项目中整洁架构的落地方案。

在这里插入图片描述

一、移动端开发者为何需要整洁架构?

绝大多数Flutter项目初期仅有少量页面、简单接口请求与setState状态更新逻辑。但当项目迭代为成熟商用产品,叠加多业务模块、离线缓存、分角色权限,以及Odoo等ERP系统对接需求后,缺乏规范架构的弊端会彻底暴露:文件代码臃肿、业务逻辑侵入界面控件、重构代码风险极高。
采用整洁架构可实现以下价值:

  1. 将业务规则与Flutter框架、第三方工具包解耦;
  2. 可独立测试业务逻辑,无需初始化页面控件、HTTP请求客户端;
  3. 无缝切换数据源(REST接口、GraphQL、本地缓存、Odoo JSON-RPC协议),无需重写业务用例;
  4. 降低新人上手成本,各分层职责清晰,便于快速读懂代码;
  5. 支撑大型代码库规模化迭代——Cybrosys旗下基于Flutter开发的Odoo全端移动套件Mobo,正是依靠这套分层架构,实现数十个业务模块的有序管理。

前置准备

在Flutter项目中落地整洁架构前,需满足以下基础条件:

  1. 已安装 Flutter 3.0 及以上版本SDK;
  2. 掌握Dart异步语法async/await、Future与Stream;
  3. 了解依赖注入基础方案(get_it、injectable、Riverpod 依赖容器均可);
  4. 选定一套状态管理方案(Bloc、Riverpod、Provider均可,架构本身无绑定)。

1. 整洁架构三层核心结构

Flutter开发场景下,整洁架构标准划分为三层:数据层(Data)、领域层(Domain)、表现层(Presentation)。
依赖流向固定:由外向内,外层可引用内层代码,内层绝对不反向依赖外层。

分层核心职责依赖对象
表现层 Presentation页面控件、状态管理Bloc/ViewModel、路由导航仅依赖领域层
领域层 Domain业务实体、业务用例、仓库抽象接口无任何外部依赖
数据层 Data仓库具体实现、远程/本地数据源、数据传输模型DTO仅依赖领域层

领域层是应用核心业务逻辑载体,仅使用纯Dart语法,不引入Flutter、http、Isar等任何外部包。这也是业务逻辑可跨端复用、轻松编写单元测试的关键。

各分层简要职责

  • 领域层Domain:定义应用业务能力。包含客户(Customer)、销售订单(SaleOrder)等业务实体,以及「拉取待交付单据」这类业务操作用例;
  • 数据层Data:定义应用数据获取与持久化方式。封装REST客户端、Odoo RPC服务、Isar/Hive本地数据库,完成数据模型与业务实体转换;
  • 表现层Presentation:定义页面展示与交互响应。存放页面控件、Bloc/Cubit状态管理器、导航路由、通用组件。

2. 推荐目录结构

规范统一的目录布局能大幅降低长期维护成本。以下目录结构可从小型项目平滑扩展至Mobo这类多模块大型套件,Odoo各业务模块(销售、库存、人事、外勤服务)均可拆分为独立垂直业务切片。

lib/
├── core/                      # 全局公共核心工具
│   ├── error/                 # 统一异常、业务失败类型定义
│   │   ├── failures.dart      # 业务层失败抽象类
│   │   └── exceptions.dart     # 底层数据源异常
│   ├── network/
│   │   └── network_info.dart   # 网络状态检测工具
│   └── usecase/
│       └── usecase.dart        # 业务用例基础抽象类
├── features/                   # 按业务模块垂直划分
│   └── sales/                  # 销售业务模块
│       ├── data/               # 数据层
│       │   ├── datasources/    # 远程、本地数据源
│       │   │   ├── sale_remote_data_source.dart
│       │   │   └── sale_local_data_source.dart
│       │   ├── models/         # 数据传输模型DTO(扩展领域实体)
│       │   │   └── sale_order_model.dart
│       │   └── repositories/    # 仓库接口具体实现
│       │       └── sale_repository_impl.dart
│       ├── domain/             # 领域层(纯业务,无外部依赖)
│       │   ├── entities/      # 纯业务实体
│       │   │   └── sale_order.dart
│       │   ├── repositories/  # 仓库抽象接口
│       │   │   └── sale_repository.dart
│       │   └── usecases/       # 单一业务操作用例
│       │       ├── get_sale_orders.dart
│       │       └── confirm_sale_order.dart
│       └── presentation/       # 表现层(页面、状态、组件)
│           ├── bloc/           # Bloc状态管理
│           │   ├── sale_bloc.dart
│           │   ├── sale_event.dart
│           │   └── sale_state.dart
│           ├── pages/          # 业务页面
│           │   └── sale_list_page.dart
│           └── widgets/        # 页面通用子组件
│               └── sale_order_tile.dart
└── main.dart

每个业务文件夹为独立垂直切片,销售模块所需全部代码均收敛在features/sales内。该隔离方案支持多团队并行开发不同业务,代码互不冲突。

3. 领域层:业务实体与业务用例

业务实体(Entity)为纯粹Dart类,仅描述业务概念,不包含JSON序列化、数据库注解、Flutter相关代码。

// features/sales/domain/entities/sale_order.dart
import 'package:equatable/equatable.dart';

class SaleOrder extends Equatable {
  final int id;
  final String name;
  final String customerName;
  final double totalAmount;
  final String state;
  final DateTime orderDate;

  const SaleOrder({
    required this.id,
    required this.name,
    required this.customerName,
    required this.totalAmount,
    required this.state,
    required this.orderDate,
  });

  
  List<Object?> get props => [id, name, customerName, totalAmount, state, orderDate];
}

业务用例(UseCase):一个用例对应单一、明确的业务操作,命名直观,业务意图一目了然。
先定义全局统一失败类型与底层异常:

// core/error/failures.dart
abstract class Failure {
  final String message;
  const Failure(this.message);
}
// 服务端接口异常对应的业务失败
class ServerFailure extends Failure {
  const ServerFailure(super.message);
}
// 本地缓存异常对应的业务失败
class CacheFailure extends Failure {
  const CacheFailure(super.message);
}

// core/error/exceptions.dart
// 底层数据源抛出的原始异常
class ServerException implements Exception {}
class CacheException implements Exception {}

// core/usecase/usecase.dart
import 'package:dartz/dartz.dart';
import '../error/failures.dart';

// 业务用例基础抽象:Type返回数据类型,Params入参类型
abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}

// 无入参标识类
class NoParams {
  const NoParams();
}

销售订单查询业务用例实现:

// features/sales/domain/usecases/get_sale_orders.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/usecase/usecase.dart';
import '../entities/sale_order.dart';
import '../repositories/sale_repository.dart';

class GetSaleOrders implements UseCase<List<SaleOrder>, NoParams> {
  final SaleRepository repository;

  GetSaleOrders(this.repository);

  
  Future<Either<Failure, List<SaleOrder>>> call(NoParams params) {
    // 直接调用仓库抽象接口,无需关心数据来源
    return repository.getSaleOrders();
  }
}

仓库抽象接口同样存放于领域层,仅定义数据操作规范,不关心数据获取实现方式:

// features/sales/domain/repositories/sale_repository.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/failures.dart';
import '../entities/sale_order.dart';

abstract interface class SaleRepository {
  Future<Either<Failure, List<SaleOrder>>> getSaleOrders();
  Future<Either<Failure, SaleOrder>> confirmSaleOrder(int id);
}

4. 数据层:数据模型、数据源、仓库实现

数据模型(Model)继承领域实体,新增序列化、接口映射逻辑。序列化逻辑收敛在数据层,保证领域层干净无JSON相关冗余代码。

// core/network/network_info.dart
abstract interface class NetworkInfo {
  Future<bool> get isConnected;
}

// 本地数据源抽象
abstract interface class SaleLocalDataSource {
  Future<void> cacheSaleOrders(List<SaleOrderModel> orders);
  Future<List<SaleOrderModel>> getCachedSaleOrders();
}

// features/sales/data/models/sale_order_model.dart
import '../../domain/entities/sale_order.dart';

class SaleOrderModel extends SaleOrder {
  const SaleOrderModel({
    required super.id,
    required super.name,
    required super.customerName,
    required super.totalAmount,
    required super.state,
    required super.orderDate,
  });

  // Odoo后端JSON映射构造函数
  factory SaleOrderModel.fromJson(Map<String, dynamic> json) {
    final partner = json['partner_id'];
    return SaleOrderModel(
      id: json['id'] as int,
      name: json['name'] as String,
      // Odoo中partner_id为数组格式 [id, 客户名称]
      customerName: partner is List ? partner[1] as String : '',
      totalAmount: (json['amount_total'] as num).toDouble(),
      state: json['state'] as String,
      orderDate: DateTime.parse(json['date_order'] as String),
    );
  }
}

数据源:单一技术载体的薄封装,分为远程数据源(对接Odoo JSON-RPC)、本地数据源(Isar/Hive持久化)。

// features/sales/data/datasources/sale_remote_data_source.dart
import 'package:odoo_rpc/odoo_rpc.dart';
import '../models/sale_order_model.dart';

abstract class SaleRemoteDataSource {
  Future<List<SaleOrderModel>> fetchSaleOrders();
}

class SaleRemoteDataSourceImpl implements SaleRemoteDataSource {
  final OdooClient client;

  SaleRemoteDataSourceImpl(this.client);

  
  Future<List<SaleOrderModel>> fetchSaleOrders() async {
    // 调用Odoo RPC查询销售订单
    final response = await client.callKw({
      'model': 'sale.order',
      'method': 'search_read',
      'args': [
        [['state', 'in', ['sale', 'done']]],
      ],
      'kwargs': {
        'fields': ['id', 'name', 'partner_id', 'amount_total', 'state', 'date_order'],
        'limit': 100,
        'order': 'date_order DESC',
      },
    });

    return (response as List)
        .map((json) => SaleOrderModel.fromJson(json as Map<String, dynamic>))
        .toList();
  }
}

仓库实现类:整合远程+本地数据源,处理离线优先缓存逻辑,捕获底层异常并转换为领域层可识别的Failure失败对象。

// features/sales/data/repositories/sale_repository_impl.dart
import 'package:dartz/dartz.dart';
import '../../../../core/error/exceptions.dart';
import '../../../../core/error/failures.dart';
import '../../../../core/network/network_info.dart';
import '../../domain/entities/sale_order.dart';
import '../../domain/repositories/sale_repository.dart';
import '../datasources/sale_local_data_source.dart';
import '../datasources/sale_remote_data_source.dart';

class SaleRepositoryImpl implements SaleRepository {
  final SaleRemoteDataSource remote;
  final SaleLocalDataSource local;
  final NetworkInfo networkInfo;

  SaleRepositoryImpl({
    required this.remote,
    required this.local,
    required this.networkInfo,
  });

  
  Future<Either<Failure, List<SaleOrder>>> getSaleOrders() async {
    // 在线:拉取远程数据并更新本地缓存
    if (await networkInfo.isConnected) {
      try {
        final orders = await remote.fetchSaleOrders();
        await local.cacheSaleOrders(orders);
        return Right(orders);
      } on ServerException {
        return Left(ServerFailure('加载销售订单失败'));
      }
    } 
    // 离线:读取本地缓存数据
    else {
      try {
        final cached = await local.getCachedSaleOrders();
        return Right(cached);
      } on CacheException {
        return Left(CacheFailure('无可用本地缓存数据'));
      }
    }
  }
}

这套「有网请求远端、无网读取缓存」的离线优先策略,已在Mobo外勤、交付模块全面落地——外勤人员常处于弱网环境,该架构完美适配场景需求。

5. 表现层:Bloc状态管理与页面控件

表现层仅依赖业务用例,向外暴露页面状态供控件渲染。以下基于flutter_bloc实现:

事件定义:

// sale_event.dart
abstract class SaleEvent {}
// 加载销售订单事件
class LoadSaleOrders extends SaleEvent {}

状态定义:

// sale_state.dart
abstract class SaleState {}
// 初始状态
class SaleInitial extends SaleState {}
// 加载中
class SaleLoading extends SaleState {}
// 加载成功(携带订单数据)
class SaleLoaded extends SaleState {
  final List<SaleOrder> orders;
  SaleLoaded(this.orders);
}
// 加载失败(携带错误信息)
class SaleError extends SaleState {
  final String message;
  SaleError(this.message);
}

Bloc业务状态控制器:

// features/sales/presentation/bloc/sale_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../core/usecase/usecase.dart';
import '../../domain/usecases/get_sale_orders.dart';
import 'sale_event.dart';
import 'sale_state.dart';

class SaleBloc extends Bloc<SaleEvent, SaleState> {
  final GetSaleOrders getSaleOrders;

  SaleBloc({required this.getSaleOrders}) : super(SaleInitial()) {
    on<LoadSaleOrders>((event, emit) async {
      emit(SaleLoading());
      final result = await getSaleOrders(const NoParams());
      // 处理用例返回结果:失败/成功分支分发状态
      result.fold(
        (failure) => emit(SaleError(failure.message)),
        (orders) => emit(SaleLoaded(orders)),
      );
    });
  }
}

页面控件完全不感知Odoo客户端、HTTP、本地数据库,仅监听Bloc状态渲染界面:

// features/sales/presentation/pages/sale_list_page.dart
class SaleListPage extends StatelessWidget {
  const SaleListPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('销售订单')),
      body: BlocBuilder<SaleBloc, SaleState>(
        builder: (context, state) {
          if (state is SaleLoading) {
            return const Center(child: CircularProgressIndicator());
          } else if (state is SaleLoaded) {
            return ListView.builder(
              itemCount: state.orders.length,
              itemBuilder: (_, i) => SaleOrderTile(order: state.orders[i]),
            );
          } else if (state is SaleError) {
            return Center(child: Text(state.message));
          }
          return const SizedBox.shrink();
        },
      ),
    );
  }
}

页面路由处注入Bloc,初始化触发数据加载:

BlocProvider(
  create: (_) => sl<SaleBloc>()..add(LoadSaleOrders()),
  child: const SaleListPage(),
)

6. 依赖注入容器

通过get_it统一管理全局依赖,实现各层运行时解耦,所有实例统一注册管理:

// injection_container.dart
import 'package:get_it/get_it.dart';

// 全局依赖容器实例
final sl = GetIt.instance;

Future<void> init() async {
  // 1. 状态层 Bloc
  sl.registerFactory(() => SaleBloc(getSaleOrders: sl()));

  // 2. 业务用例
  sl.registerLazySingleton(() => GetSaleOrders(sl()));

  // 3. 领域仓库抽象实现
  sl.registerLazySingleton<SaleRepository>(
    () => SaleRepositoryImpl(remote: sl(), local: sl(), networkInfo: sl()),
  );

  // 4. 远程/本地数据源
  sl.registerLazySingleton<SaleRemoteDataSource>(
    () => SaleRemoteDataSourceImpl(sl()),
  );
  sl.registerLazySingleton<SaleLocalDataSource>(
    () => SaleLocalDataSourceImpl(sl()),
  );

  // 5. 第三方外部依赖
  sl.registerLazySingleton(() => OdooClient('https://你的Odoo实例地址.com'));
  sl.registerLazySingleton<NetworkInfo>(() => NetworkInfoImpl(sl()));
}

7. 分层使用边界对照表

分层适用场景禁止操作
领域层 Domain业务规则、数据校验、纯逻辑计算引入Flutter控件、HTTP请求、JSON序列化
数据层 Data接口客户端、本地缓存、数据转换映射编写业务判断、业务校验逻辑
表现层 PresentationUI渲染、路由导航、用户交互监听直接调用数据源、绕开用例访问仓库

常见开发误区:页面直接调用仓库接口。该写法虽能运行,但会跳过业务用例层,丢失业务统一收口、单元测试入口,破坏分层规范。所有页面数据请求必须经由业务用例中转。

总结

整洁架构将Flutter项目重构为模块化、高可测、可长期迭代的工程体系。通过三层分层隔离关注点,实现各模块独立演进:

  1. 业务实体+用例:使用纯Dart承载业务核心诉求;
  2. 仓库抽象接口:屏蔽底层数据源差异;
  3. Bloc/页面:轻量化,仅负责界面交互渲染;
  4. 依赖注入:统一在应用入口组装所有依赖。

无论你开发企业内部人事工具、面向客户CRM系统,还是Mobo这类配套Odoo的大型移动套件,整洁架构都能保障项目多年持续稳定迭代。落地无需一次性改造全项目:可先选取单一业务模块完成三层拆分,其余业务参照统一规范逐步重构。

更多推荐