一、引言
在 Android 开发的架构设计中,领域层(Domain Layer)扮演着至关重要的角色。它是应用程序的核心业务逻辑所在之处,负责处理业务规则、协调数据流动以及实现用例。Android Room 框架虽然主要聚焦于数据持久化,但其与领域层的交互紧密且关键。领域层需要借助 Room 框架提供的数据访问能力来实现业务功能,同时又要将业务逻辑与数据访问细节隔离开来,以保证代码的可维护性、可测试性和可扩展性。
本文将深入剖析 Android Room 框架在领域层的应用和实现原理,从源码级别详细分析领域层中与 Room 相关的各个组件和流程。通过对源码的解读,我们可以更好地理解如何在领域层中合理运用 Room 框架,以及如何构建高效、健壮的业务逻辑。
二、领域层概述
2.1 领域层的职责
领域层的主要职责是实现应用程序的核心业务逻辑。它不关心数据的来源(如数据库、网络等)和展示形式(如 UI 界面),只专注于业务规则的处理。具体来说,领域层的职责包括:
- 业务规则处理:实现各种业务规则,如用户注册、登录验证、数据计算等。
- 用例实现:将业务需求转化为具体的用例,每个用例代表一个完整的业务流程。
- 数据协调:协调不同数据源之间的数据流动,确保数据的一致性和完整性。
2.2 领域层与其他层的关系
在典型的 Android 架构中,领域层位于数据层(Data Layer)和表现层(Presentation Layer)之间。数据层负责提供数据的存储和访问功能,表现层负责将数据展示给用户。领域层作为中间层,起到了桥梁的作用,它从数据层获取数据,处理业务逻辑,然后将结果传递给表现层。
2.3 Room 框架在领域层的作用
Room 框架为领域层提供了便捷的数据访问能力。领域层可以通过 Room 的 DAO(Data Access Object)接口来操作数据库,获取和存储数据。同时,Room 的实体类和数据库定义也为领域层提供了数据模型的基础。领域层可以基于这些数据模型实现业务逻辑,而无需关心数据库操作的细节。
三、领域层中的数据模型
3.1 实体类与领域模型
在 Room 框架中,实体类(Entity)用于定义数据库表的结构。而在领域层中,我们通常会使用领域模型(Domain Model)来表示业务数据。领域模型和实体类有一定的关联,但又不完全相同。领域模型更侧重于业务概念,而实体类更侧重于数据库存储。
以下是一个简单的实体类和领域模型的示例:
java
// Room 实体类,用于定义数据库表结构
import androidx.room.Entity;
import androidx.room.PrimaryKey;
// 使用 @Entity 注解标记该类为数据库实体类,对应数据库中的 "users" 表
@Entity(tableName = "users")
public class UserEntity {
// 使用 @PrimaryKey 注解指定该字段为主键,autoGenerate = true 表示主键自动生成
@PrimaryKey(autoGenerate = true)
private int id;
private String name;
private int age;
// 构造函数
public UserEntity(String name, int age) {
this.name = name;
this.age = age;
}
// Getter 和 Setter 方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 领域模型,用于表示业务概念
public class User {
private int id;
private String name;
private int age;
// 构造函数
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
// Getter 和 Setter 方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3.2 数据转换
由于实体类和领域模型存在差异,我们需要在它们之间进行数据转换。通常,我们会在领域层中实现一个转换器(Mapper)来完成这个任务。
java
// 数据转换器,用于将实体类转换为领域模型,以及将领域模型转换为实体类
public class UserMapper {
// 将 UserEntity 转换为 User
public static User map(UserEntity userEntity) {
return new User(userEntity.getId(), userEntity.getName(), userEntity.getAge());
}
// 将 User 转换为 UserEntity
public static UserEntity map(User user) {
UserEntity userEntity = new UserEntity(user.getName(), user.getAge());
userEntity.setId(user.getId());
return userEntity;
}
}
3.3 源码分析
从源码的角度来看,实体类和领域模型的定义是简单的 Java 类,通过注解和构造函数来实现其功能。而数据转换器则是一个普通的工具类,通过静态方法来完成数据的转换。这些类的实现非常直观,主要是为了将数据在不同的表示形式之间进行转换,以满足领域层和数据层的不同需求。
四、领域层中的用例实现
4.1 用例的概念
用例(UseCase)是领域层中的一个重要概念,它代表了一个完整的业务流程。每个用例通常包含一个或多个业务规则,并且会调用数据层的接口来获取或存储数据。
4.2 用例的实现方式
在领域层中,我们通常会使用一个单独的类来实现每个用例。这个类通常包含一个执行方法,用于执行该用例的业务逻辑。
以下是一个简单的用例示例,用于获取所有用户:
java
// 获取所有用户的用例类
import java.util.List;
// 该类负责获取所有用户的业务逻辑
public class GetAllUsersUseCase {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public GetAllUsersUseCase(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,用于获取所有用户
public List<User> execute() {
// 调用 UserRepository 的 getAllUsers 方法获取所有用户实体
List<UserEntity> userEntities = userRepository.getAllUsers();
// 使用 UserMapper 将用户实体转换为领域模型
return UserMapper.map(userEntities);
}
}
4.3 用例与数据层的交互
在用例的实现中,我们通常会依赖一个数据仓库(Repository)来与数据层进行交互。数据仓库是领域层和数据层之间的桥梁,它负责封装数据的来源和访问细节。
java
// 用户数据仓库接口
import java.util.List;
// 该接口定义了与用户数据相关的操作方法
public interface UserRepository {
// 获取所有用户实体
List<UserEntity> getAllUsers();
}
4.4 源码分析
从源码的角度来看,用例类是一个普通的 Java 类,通过构造函数注入数据仓库实例,然后在执行方法中调用数据仓库的接口来获取数据。数据仓库接口定义了与数据层交互的方法,具体的实现可以在数据层中完成。这种设计模式使得领域层和数据层之间的耦合度降低,提高了代码的可维护性和可测试性。
五、领域层中的业务规则处理
5.1 业务规则的定义
业务规则是领域层的核心内容,它定义了应用程序的各种业务逻辑。例如,用户注册时的密码强度验证、数据计算时的算法等。
5.2 业务规则的实现方式
在领域层中,我们通常会将业务规则封装在一个或多个方法中,这些方法可以在不同的用例中被调用。
以下是一个简单的业务规则示例,用于验证用户的年龄是否合法:
java
// 用户业务规则类
public class UserBusinessRules {
// 最小合法年龄
private static final int MIN_LEGAL_AGE = 18;
// 验证用户年龄是否合法
public static boolean isUserAgeValid(User user) {
return user.getAge() >= MIN_LEGAL_AGE;
}
}
5.3 在用例中应用业务规则
在实际的用例实现中,我们可以调用业务规则方法来验证数据的合法性。
java
// 创建用户的用例类
public class CreateUserUseCase {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public CreateUserUseCase(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,用于创建用户
public boolean execute(User user) {
// 验证用户年龄是否合法
if (!UserBusinessRules.isUserAgeValid(user)) {
return false;
}
// 将用户领域模型转换为实体类
UserEntity userEntity = UserMapper.map(user);
// 调用 UserRepository 的 createUser 方法创建用户
userRepository.createUser(userEntity);
return true;
}
}
5.4 源码分析
从源码的角度来看,业务规则类是一个普通的工具类,通过静态方法来实现业务规则的验证。在用例类中,我们可以调用这些业务规则方法来确保数据的合法性,从而保证业务逻辑的正确性。这种设计模式使得业务规则的实现和管理更加清晰,易于维护和扩展。
六、领域层中的数据协调
6.1 数据协调的概念
数据协调是指在领域层中协调不同数据源之间的数据流动,确保数据的一致性和完整性。在使用 Room 框架的情况下,数据协调主要涉及到数据库操作和业务逻辑的协调。
6.2 数据协调的实现方式
在领域层中,我们可以通过事务管理和数据同步机制来实现数据协调。
6.2.1 事务管理
事务管理是指将一组数据库操作作为一个原子操作来执行,要么全部成功,要么全部失败。在 Room 框架中,我们可以使用 @Transaction
注解来实现事务管理。
java
// 用户数据仓库的实现类
import androidx.room.RoomDatabase;
import androidx.room.Transaction;
import java.util.List;
// 该类实现了 UserRepository 接口,负责与用户数据相关的数据库操作
public class UserRepositoryImpl implements UserRepository {
private final AppDatabase appDatabase;
// 构造函数,注入 AppDatabase 实例
public UserRepositoryImpl(AppDatabase appDatabase) {
this.appDatabase = appDatabase;
}
// 使用 @Transaction 注解标记该方法为事务方法
@Transaction
@Override
public void createUser(UserEntity userEntity) {
// 开始事务
appDatabase.beginTransaction();
try {
// 插入用户数据
appDatabase.userDao().insertUser(userEntity);
// 设置事务成功
appDatabase.setTransactionSuccessful();
} finally {
// 结束事务
appDatabase.endTransaction();
}
}
@Override
public List<UserEntity> getAllUsers() {
// 查询所有用户数据
return appDatabase.userDao().getAllUsers();
}
}
6.2.2 数据同步机制
数据同步机制是指在不同数据源之间保持数据的一致性。在使用 Room 框架的情况下,我们可以通过监听数据库的变化来实现数据同步。
java
// 用户数据观察者类
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import java.util.List;
// 该类用于观察用户数据的变化,并在数据变化时执行相应的操作
public class UserDataObserver implements Observer<List<UserEntity>> {
private final UserDataChangeListener userDataChangeListener;
// 构造函数,注入 UserDataChangeListener 实例
public UserDataObserver(UserDataChangeListener userDataChangeListener) {
this.userDataChangeListener = userDataChangeListener;
}
@Override
public void onChanged(List<UserEntity> userEntities) {
// 当用户数据发生变化时,调用 UserDataChangeListener 的 onUserDataChanged 方法
userDataChangeListener.onUserDataChanged(UserMapper.map(userEntities));
}
// 用户数据变化监听器接口
public interface UserDataChangeListener {
// 当用户数据发生变化时调用该方法
void onUserDataChanged(List<User> users);
}
}
6.3 源码分析
从源码的角度来看,事务管理是通过 Room 框架的 @Transaction
注解和数据库的事务方法来实现的。数据同步机制是通过 LiveData
和 Observer
来实现的,当数据库中的数据发生变化时,LiveData
会通知所有的 Observer
,从而实现数据的同步。这种设计模式确保了数据的一致性和完整性,提高了应用程序的稳定性。
七、领域层中的错误处理
7.1 错误处理的重要性
在领域层中,错误处理是非常重要的。由于领域层负责处理核心业务逻辑,任何错误都可能导致业务流程的中断或数据的不一致。因此,我们需要在领域层中实现完善的错误处理机制,以确保应用程序的健壮性。
7.2 错误类型的定义
在领域层中,我们通常会定义一些特定的错误类型,以便于区分不同的错误情况。
java
// 领域层错误类型枚举
public enum DomainError {
// 用户年龄不合法错误
USER_AGE_INVALID,
// 数据库操作失败错误
DATABASE_OPERATION_FAILED
}
7.3 错误处理的实现方式
在领域层中,我们可以通过抛出异常或返回错误码的方式来处理错误。
7.3.1 抛出异常
java
// 创建用户的用例类,使用抛出异常的方式处理错误
public class CreateUserUseCaseWithException {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public CreateUserUseCaseWithException(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,用于创建用户
public void execute(User user) throws DomainException {
// 验证用户年龄是否合法
if (!UserBusinessRules.isUserAgeValid(user)) {
// 若年龄不合法,抛出 DomainException 异常
throw new DomainException(DomainError.USER_AGE_INVALID);
}
// 将用户领域模型转换为实体类
UserEntity userEntity = UserMapper.map(user);
try {
// 调用 UserRepository 的 createUser 方法创建用户
userRepository.createUser(userEntity);
} catch (Exception e) {
// 若数据库操作失败,抛出 DomainException 异常
throw new DomainException(DomainError.DATABASE_OPERATION_FAILED);
}
}
// 领域层异常类
public static class DomainException extends Exception {
private final DomainError domainError;
// 构造函数,传入 DomainError 类型的错误
public DomainException(DomainError domainError) {
this.domainError = domainError;
}
// 获取 DomainError 类型的错误
public DomainError getDomainError() {
return domainError;
}
}
}
7.3.2 返回错误码
java
// 创建用户的用例类,使用返回错误码的方式处理错误
public class CreateUserUseCaseWithErrorCode {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public CreateUserUseCaseWithErrorCode(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,用于创建用户
public DomainError execute(User user) {
// 验证用户年龄是否合法
if (!UserBusinessRules.isUserAgeValid(user)) {
// 若年龄不合法,返回 USER_AGE_INVALID 错误码
return DomainError.USER_AGE_INVALID;
}
// 将用户领域模型转换为实体类
UserEntity userEntity = UserMapper.map(user);
try {
// 调用 UserRepository 的 createUser 方法创建用户
userRepository.createUser(userEntity);
// 若操作成功,返回 null
return null;
} catch (Exception e) {
// 若数据库操作失败,返回 DATABASE_OPERATION_FAILED 错误码
return DomainError.DATABASE_OPERATION_FAILED;
}
}
}
7.4 源码分析
从源码的角度来看,错误处理可以通过抛出异常或返回错误码的方式来实现。抛出异常的方式可以让调用者更方便地捕获和处理错误,而返回错误码的方式则更加简洁明了。在实际应用中,我们可以根据具体的需求选择合适的错误处理方式。
八、领域层中的测试
8.1 测试的重要性
领域层作为应用程序的核心业务逻辑所在之处,其正确性直接影响到整个应用程序的功能和稳定性。因此,对领域层进行全面的测试是非常必要的。
8.2 测试框架的选择
在 Android 开发中,我们可以使用 JUnit 和 Mockito 等测试框架来对领域层进行单元测试。
8.3 单元测试示例
以下是一个对 CreateUserUseCase
进行单元测试的示例:
java
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
// CreateUserUseCase 类的单元测试类
public class CreateUserUseCaseTest {
// 使用 @Mock 注解创建 UserRepository 的模拟对象
@Mock
private UserRepository userRepository;
private CreateUserUseCase createUserUseCase;
// 在每个测试方法执行前进行初始化操作
@Before
public void setUp() {
// 初始化 Mockito 注解
MockitoAnnotations.initMocks(this);
// 创建 CreateUserUseCase 实例,注入模拟的 UserRepository 对象
createUserUseCase = new CreateUserUseCase(userRepository);
}
// 测试创建用户成功的情况
@Test
public void testCreateUserSuccess() {
// 创建一个合法的用户对象
User user = new User(0, "John", 20);
// 将用户对象转换为 UserEntity 对象
UserEntity userEntity = UserMapper.map(user);
// 当调用 userRepository 的 createUser 方法时,不抛出异常
when(userRepository.createUser(userEntity)).thenReturn(true);
// 调用 createUserUseCase 的 execute 方法创建用户
boolean result = createUserUseCase.execute(user);
// 验证创建用户是否成功
assertEquals(true, result);
}
// 测试创建用户失败(年龄不合法)的情况
@Test
public void testCreateUserFailedDueToInvalidAge() {
// 创建一个年龄不合法的用户对象
User user = new User(0, "John", 10);
// 调用 createUserUseCase 的 execute 方法创建用户
boolean result = createUserUseCase.execute(user);
// 验证创建用户是否失败
assertEquals(false, result);
}
}
8.4 源码分析
从源码的角度来看,单元测试主要是通过模拟数据仓库的行为来验证用例的正确性。使用 Mockito 框架可以方便地创建模拟对象,并设置其行为。通过编写不同的测试用例,我们可以覆盖用例的各种可能情况,确保业务逻辑的正确性。
九、领域层的设计模式应用
9.1 单一职责原则
单一职责原则是指一个类或模块应该只负责一项职责。在领域层中,我们可以将不同的业务逻辑封装在不同的类中,每个类只负责一个特定的业务功能。例如,用例类只负责执行具体的业务流程,业务规则类只负责验证业务规则,数据转换器类只负责数据的转换等。
java
// 单一职责原则示例:用例类只负责执行获取所有用户的业务流程
public class GetAllUsersUseCase {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public GetAllUsersUseCase(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,用于获取所有用户
public List<User> execute() {
// 调用 UserRepository 的 getAllUsers 方法获取所有用户实体
List<UserEntity> userEntities = userRepository.getAllUsers();
// 使用 UserMapper 将用户实体转换为领域模型
return UserMapper.map(userEntities);
}
}
// 单一职责原则示例:业务规则类只负责验证用户年龄是否合法
public class UserBusinessRules {
// 最小合法年龄
private static final int MIN_LEGAL_AGE = 18;
// 验证用户年龄是否合法
public static boolean isUserAgeValid(User user) {
return user.getAge() >= MIN_LEGAL_AGE;
}
}
// 单一职责原则示例:数据转换器类只负责数据的转换
public class UserMapper {
// 将 UserEntity 转换为 User
public static User map(UserEntity userEntity) {
return new User(userEntity.getId(), userEntity.getName(), userEntity.getAge());
}
// 将 User 转换为 UserEntity
public static UserEntity map(User user) {
UserEntity userEntity = new UserEntity(user.getName(), user.getAge());
userEntity.setId(user.getId());
return userEntity;
}
}
9.2 开闭原则
开闭原则是指一个类或模块应该对扩展开放,对修改关闭。在领域层中,我们可以通过接口和抽象类来实现开闭原则。例如,数据仓库接口定义了与数据层交互的方法,具体的实现可以在不同的类中完成。当需要添加新的数据源或修改数据访问方式时,我们只需要实现新的数据仓库类,而不需要修改用例类的代码。
java
// 开闭原则示例:数据仓库接口
public interface UserRepository {
// 获取所有用户实体
List<UserEntity> getAllUsers();
// 创建用户实体
void createUser(UserEntity userEntity);
}
// 开闭原则示例:Room 数据仓库实现类
public class RoomUserRepository implements UserRepository {
private final AppDatabase appDatabase;
// 构造函数,注入 AppDatabase 实例
public RoomUserRepository(AppDatabase appDatabase) {
this.appDatabase = appDatabase;
}
@Override
public List<UserEntity> getAllUsers() {
// 查询所有用户数据
return appDatabase.userDao().getAllUsers();
}
@Override
public void createUser(UserEntity userEntity) {
// 插入用户数据
appDatabase.userDao().insertUser(userEntity);
}
}
// 开闭原则示例:用例类使用数据仓库接口
public class CreateUserUseCase {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public CreateUserUseCase(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,用于创建用户
public boolean execute(User user) {
// 验证用户年龄是否合法
if (!UserBusinessRules.isUserAgeValid(user)) {
return false;
}
// 将用户领域模型转换为实体类
UserEntity userEntity = UserMapper.map(user);
// 调用 UserRepository 的 createUser 方法创建用户
userRepository.createUser(userEntity);
return true;
}
}
9.3 依赖倒置原则
依赖倒置原则是指高层模块不应该依赖低层模块,二者都应该依赖抽象。在领域层中,用例类作为高层模块,不应该直接依赖具体的数据仓库实现类,而是应该依赖数据仓库接口。这样可以降低模块之间的耦合度,提高代码的可维护性和可扩展性。
java
// 依赖倒置原则示例:用例类依赖数据仓库接口
public class GetAllUsersUseCase {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public GetAllUsersUseCase(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,用于获取所有用户
public List<User> execute() {
// 调用 UserRepository 的 getAllUsers 方法获取所有用户实体
List<UserEntity> userEntities = userRepository.getAllUsers();
// 使用 UserMapper 将用户实体转换为领域模型
return UserMapper.map(userEntities);
}
}
// 依赖倒置原则示例:数据仓库接口
public interface UserRepository {
// 获取所有用户实体
List<UserEntity> getAllUsers();
}
// 依赖倒置原则示例:Room 数据仓库实现类
public class RoomUserRepository implements UserRepository {
private final AppDatabase appDatabase;
// 构造函数,注入 AppDatabase 实例
public RoomUserRepository(AppDatabase appDatabase) {
this.appDatabase = appDatabase;
}
@Override
public List<UserEntity> getAllUsers() {
// 查询所有用户数据
return appDatabase.userDao().getAllUsers();
}
}
9.4 源码分析
从源码的角度来看,设计模式的应用可以使领域层的代码更加清晰、可维护和可扩展。单一职责原则将不同的业务逻辑分离,开闭原则通过接口和抽象类实现了代码的扩展性,依赖倒置原则降低了模块之间的耦合度。这些设计模式的应用符合面向对象编程的最佳实践,有助于构建高质量的领域层代码。
十、领域层与异步操作
10.1 异步操作的需求
在领域层中,有些业务逻辑可能涉及到耗时的操作,例如从数据库中查询大量数据、进行复杂的数据计算或者与网络服务进行交互等。如果在主线程中执行这些耗时操作,会导致应用程序界面卡顿,影响用户体验。因此,需要将这些操作放在后台线程中执行,实现异步操作。
10.2 异步操作的实现方式
10.2.1 使用线程和回调
在早期的 Android 开发中,我们可以使用线程和回调的方式来实现异步操作。以下是一个使用线程和回调来获取所有用户的示例:
java
// 获取所有用户的用例类,使用线程和回调实现异步操作
import java.util.List;
// 该类负责异步获取所有用户的业务逻辑
public class GetAllUsersUseCaseAsync {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public GetAllUsersUseCaseAsync(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,用于异步获取所有用户
public void execute(final Callback callback) {
// 创建一个新线程来执行耗时操作
new Thread(new Runnable() {
@Override
public void run() {
// 调用 UserRepository 的 getAllUsers 方法获取所有用户实体
List<UserEntity> userEntities = userRepository.getAllUsers();
// 将用户实体转换为领域模型
List<User> users = UserMapper.map(userEntities);
// 在主线程中调用回调方法返回结果
if (callback != null) {
callback.onSuccess(users);
}
}
}).start();
}
// 回调接口,用于返回异步操作的结果
public interface Callback {
// 当异步操作成功时调用该方法
void onSuccess(List<User> users);
// 当异步操作失败时调用该方法
void onError(Exception e);
}
}
10.2.2 使用 AsyncTask
AsyncTask
是 Android 提供的一个方便的异步操作类,它可以在后台线程中执行耗时操作,并在主线程中更新 UI。以下是一个使用 AsyncTask
来获取所有用户的示例:
java
import android.os.AsyncTask;
import java.util.List;
// 使用 AsyncTask 实现异步获取所有用户的用例类
public class GetAllUsersUseCaseAsyncTask extends AsyncTask<Void, Void, List<User>> {
private final UserRepository userRepository;
private final Callback callback;
// 构造函数,注入 UserRepository 实例和回调接口
public GetAllUsersUseCaseAsyncTask(UserRepository userRepository, Callback callback) {
this.userRepository = userRepository;
this.callback = callback;
}
// 在后台线程中执行耗时操作
@Override
protected List<User> doInBackground(Void... voids) {
// 调用 UserRepository 的 getAllUsers 方法获取所有用户实体
List<UserEntity> userEntities = userRepository.getAllUsers();
// 将用户实体转换为领域模型
return UserMapper.map(userEntities);
}
// 在主线程中处理异步操作的结果
@Override
protected void onPostExecute(List<User> users) {
if (callback != null) {
callback.onSuccess(users);
}
}
// 回调接口,用于返回异步操作的结果
public interface Callback {
// 当异步操作成功时调用该方法
void onSuccess(List<User> users);
// 当异步操作失败时调用该方法
void onError(Exception e);
}
}
10.2.3 使用 RxJava
RxJava 是一个用于在 Java 虚拟机上使用可观测的序列来组成异步的、基于事件的程序的库。它提供了丰富的操作符和线程调度器,可以方便地实现异步操作。以下是一个使用 RxJava 来获取所有用户的示例:
java
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.List;
// 使用 RxJava 实现异步获取所有用户的用例类
public class GetAllUsersUseCaseRxJava {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public GetAllUsersUseCaseRxJava(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行方法,返回一个 Observable 对象
public Observable<List<User>> execute() {
return Observable.fromCallable(() -> {
// 调用 UserRepository 的 getAllUsers 方法获取所有用户实体
List<UserEntity> userEntities = userRepository.getAllUsers();
// 将用户实体转换为领域模型
return UserMapper.map(userEntities);
})
.subscribeOn(Schedulers.io()) // 指定在 IO 线程中执行耗时操作
.observeOn(AndroidSchedulers.mainThread()); // 指定在主线程中处理结果
}
}
10.2.4 使用 Kotlin 协程
Kotlin 协程是一种轻量级的线程管理方式,它可以简化异步编程。以下是一个使用 Kotlin 协程来获取所有用户的示例:
kotlin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*
// 使用 Kotlin 协程实现异步获取所有用户的用例类
class GetAllUsersUseCaseCoroutine(private val userRepository: UserRepository) {
// 执行方法,使用 suspend 关键字标记为挂起函数
suspend fun execute(): List<User> {
return withContext(Dispatchers.IO) {
// 调用 UserRepository 的 getAllUsers 方法获取所有用户实体
val userEntities = userRepository.getAllUsers()
// 将用户实体转换为领域模型
UserMapper.map(userEntities)
}
}
}
10.3 源码分析
从源码的角度来看,不同的异步操作实现方式各有优缺点。线程和回调的方式比较基础,但代码比较繁琐,需要手动管理线程和回调。AsyncTask
是 Android 提供的一种方便的异步操作类,但在 Android 4.0 之后,它的性能和使用方式受到了一些限制。RxJava 提供了丰富的操作符和线程调度器,使得异步操作的代码更加简洁和灵活,但学习成本较高。Kotlin 协程是一种轻量级的线程管理方式,它的语法简洁,使用方便,是目前 Android 开发中推荐的异步操作方式。
十一、领域层中的缓存机制
11.1 缓存的作用
在领域层中,缓存机制可以提高应用程序的性能和响应速度。当需要频繁访问相同的数据时,从缓存中获取数据比从数据库或网络中获取数据要快得多。同时,缓存机制还可以减少对数据库和网络的访问次数,降低资源消耗。
11.2 缓存的实现方式
11.2.1 内存缓存
内存缓存是指将数据存储在内存中,以提高数据的访问速度。在 Java 中,我们可以使用 HashMap
或 LruCache
来实现内存缓存。以下是一个使用 LruCache
来缓存用户数据的示例:
java
import android.util.LruCache;
import java.util.List;
// 用户数据内存缓存类
public class UserMemoryCache {
private static final int CACHE_SIZE = 10; // 缓存的最大容量
private final LruCache<Integer, User> userCache;
// 构造函数,初始化 LruCache
public UserMemoryCache() {
userCache = new LruCache<Integer, User>(CACHE_SIZE) {
@Override
protected int sizeOf(Integer key, User value) {
return 1; // 每个用户对象的大小为 1
}
};
}
// 将用户对象存入缓存
public void putUser(User user) {
userCache.put(user.getId(), user);
}
// 从缓存中获取用户对象
public User getUser(int id) {
return userCache.get(id);
}
// 从缓存中获取所有用户对象
public List<User> getAllUsers() {
return userCache.snapshot().values().stream().toList();
}
// 清空缓存
public void clearCache() {
userCache.evictAll();
}
}
11.2.2 磁盘缓存
磁盘缓存是指将数据存储在磁盘上,以持久化存储数据。在 Android 中,我们可以使用 DiskLruCache
来实现磁盘缓存。以下是一个使用 DiskLruCache
来缓存用户数据的示例:
java
import android.content.Context;
import android.os.Environment;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import com.jakewharton.disklrucache.DiskLruCache;
// 用户数据磁盘缓存类
public class UserDiskCache {
private static final int APP_VERSION = 1;
private static final int VALUE_COUNT = 1;
private static final long CACHE_SIZE = 10 * 1024 * 1024; // 缓存的最大容量为 10MB
private final DiskLruCache diskLruCache;
// 构造函数,初始化 DiskLruCache
public UserDiskCache(Context context) throws IOException {
File cacheDir = getDiskCacheDir(context, "user_cache");
diskLruCache = DiskLruCache.open(cacheDir, APP_VERSION, VALUE_COUNT, CACHE_SIZE);
}
// 获取磁盘缓存目录
private File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
// 将用户对象存入磁盘缓存
public void putUser(User user) throws IOException {
String key = String.valueOf(user.getId());
DiskLruCache.Editor editor = diskLruCache.edit(key);
if (editor != null) {
OutputStream outputStream = editor.newOutputStream(0);
// 将用户对象转换为字节流并写入输出流
// 这里需要实现具体的序列化逻辑
editor.commit();
}
}
// 从磁盘缓存中获取用户对象
public User getUser(int id) throws IOException {
String key = String.valueOf(id);
DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
if (snapshot != null) {
// 从输入流中读取用户对象的字节流并反序列化
// 这里需要实现具体的反序列化逻辑
snapshot.close();
}
return null;
}
// 清空磁盘缓存
public void clearCache() throws IOException {
diskLruCache.delete();
}
}
11.3 缓存策略
在使用缓存机制时,需要制定合适的缓存策略,以确保缓存数据的有效性和一致性。常见的缓存策略包括:
- 缓存过期策略:为缓存数据设置过期时间,当数据过期时,从数据库或网络中重新获取数据。
- 缓存更新策略:当数据库或网络中的数据发生变化时,及时更新缓存中的数据。
- 缓存淘汰策略:当缓存达到最大容量时,根据一定的规则淘汰部分缓存数据,例如最近最少使用(LRU)策略。
11.4 源码分析
从源码的角度来看,内存缓存和磁盘缓存的实现方式各有特点。内存缓存使用 LruCache
可以方便地实现缓存的管理和淘汰策略,而磁盘缓存使用 DiskLruCache
可以实现数据的持久化存储。在实际应用中,我们可以根据具体的需求选择合适的缓存方式和缓存策略。
十二、领域层中的事件驱动架构
12.1 事件驱动架构的概念
事件驱动架构(Event-Driven Architecture,EDA)是一种软件架构模式,它通过事件的发布和订阅机制来实现组件之间的解耦。在领域层中,事件驱动架构可以用于处理业务逻辑中的各种事件,例如用户注册成功事件、数据更新事件等。
12.2 事件驱动架构的实现方式
在 Java 中,我们可以使用观察者模式来实现事件驱动架构。以下是一个简单的事件驱动架构示例:
java
import java.util.ArrayList;
import java.util.List;
// 事件接口
interface Event {
// 获取事件类型
String getEventType();
}
// 用户注册成功事件类
class UserRegistrationSuccessEvent implements Event {
private final User user;
// 构造函数,传入注册成功的用户对象
public UserRegistrationSuccessEvent(User user) {
this.user = user;
}
// 获取事件类型
@Override
public String getEventType() {
return "UserRegistrationSuccess";
}
// 获取注册成功的用户对象
public User getUser() {
return user;
}
}
// 事件监听器接口
interface EventListener {
// 处理事件的方法
void onEvent(Event event);
}
// 事件发布者类
class EventPublisher {
private final List<EventListener> listeners;
// 构造函数,初始化事件监听器列表
public EventPublisher() {
listeners = new ArrayList<>();
}
// 注册事件监听器
public void registerListener(EventListener listener) {
listeners.add(listener);
}
// 取消注册事件监听器
public void unregisterListener(EventListener listener) {
listeners.remove(listener);
}
// 发布事件
public void publishEvent(Event event) {
for (EventListener listener : listeners) {
listener.onEvent(event);
}
}
}
// 领域层中的用例类,使用事件驱动架构
class UserRegistrationUseCase {
private final UserRepository userRepository;
private final EventPublisher eventPublisher;
// 构造函数,注入 UserRepository 实例和 EventPublisher 实例
public UserRegistrationUseCase(UserRepository userRepository, EventPublisher eventPublisher) {
this.userRepository = userRepository;
this.eventPublisher = eventPublisher;
}
// 执行用户注册的方法
public void registerUser(User user) {
// 将用户对象转换为 UserEntity 对象
UserEntity userEntity = UserMapper.map(user);
// 调用 userRepository 的 createUser 方法创建用户
userRepository.createUser(userEntity);
// 发布用户注册成功事件
eventPublisher.publishEvent(new UserRegistrationSuccessEvent(user));
}
}
// 事件监听器实现类
class UserRegistrationSuccessListener implements EventListener {
@Override
public void onEvent(Event event) {
if (event instanceof UserRegistrationSuccessEvent) {
UserRegistrationSuccessEvent registrationEvent = (UserRegistrationSuccessEvent) event;
User user = registrationEvent.getUser();
// 处理用户注册成功事件,例如发送欢迎邮件等
System.out.println("User " + user.getName() + " registered successfully!");
}
}
}
12.3 源码分析
从源码的角度来看,事件驱动架构通过事件接口、事件监听器接口和事件发布者类实现了组件之间的解耦。事件发布者类负责发布事件,事件监听器类负责处理事件。在领域层中,用例类可以通过事件发布者类发布事件,其他组件可以通过注册事件监听器来处理这些事件。这种架构模式使得领域层的代码更加灵活和可扩展。
十三、领域层中的状态管理
13.1 状态管理的需求
在领域层中,有些业务逻辑可能涉及到状态的管理,例如用户的登录状态、订单的处理状态等。状态管理可以确保业务逻辑的正确性和一致性,同时也可以提高代码的可维护性。
13.2 状态管理的实现方式
13.2.1 枚举类型
在 Java 中,我们可以使用枚举类型来管理状态。以下是一个使用枚举类型来管理用户登录状态的示例:
java
// 用户登录状态枚举类型
enum UserLoginState {
// 未登录状态
NOT_LOGGED_IN,
// 登录中状态
LOGGING_IN,
// 已登录状态
LOGGED_IN
}
// 用户登录用例类,使用状态管理
class UserLoginUseCase {
private UserLoginState currentState = UserLoginState.NOT_LOGGED_IN;
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public UserLoginUseCase(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行用户登录的方法
public void login(String username, String password) {
if (currentState == UserLoginState.NOT_LOGGED_IN) {
currentState = UserLoginState.LOGGING_IN;
// 调用 userRepository 的验证用户登录信息的方法
boolean isLoggedIn = userRepository.validateUserLogin(username, password);
if (isLoggedIn) {
currentState = UserLoginState.LOGGED_IN;
// 处理登录成功的逻辑
} else {
currentState = UserLoginState.NOT_LOGGED_IN;
// 处理登录失败的逻辑
}
}
}
// 获取当前用户登录状态
public UserLoginState getCurrentState() {
return currentState;
}
}
13.2.2 状态模式
状态模式是一种行为设计模式,它允许对象在其内部状态改变时改变它的行为。以下是一个使用状态模式来管理用户登录状态的示例:
java
// 用户登录状态接口
interface UserLoginState {
// 处理用户登录的方法
void handleLogin(UserLoginContext context, String username, String password);
// 处理用户注销的方法
void handleLogout(UserLoginContext context);
}
// 未登录状态类
class NotLoggedInState implements UserLoginState {
@Override
public void handleLogin(UserLoginContext context, String username, String password) {
context.setCurrentState(new LoggingInState());
// 调用 userRepository 的验证用户登录信息的方法
boolean isLoggedIn = context.getUserRepository().validateUserLogin(username, password);
if (isLoggedIn) {
context.setCurrentState(new LoggedInState());
// 处理登录成功的逻辑
} else {
context.setCurrentState(new NotLoggedInState());
// 处理登录失败的逻辑
}
}
@Override
public void handleLogout(UserLoginContext context) {
// 未登录状态下不能注销,不做任何处理
}
}
// 登录中状态类
class LoggingInState implements UserLoginState {
@Override
public void handleLogin(UserLoginContext context, String username, String password) {
// 登录中状态下不能再次登录,不做任何处理
}
@Override
public void handleLogout(UserLoginContext context) {
// 登录中状态下不能注销,不做任何处理
}
}
// 已登录状态类
class LoggedInState implements UserLoginState {
@Override
public void handleLogin(UserLoginContext context, String username, String password) {
// 已登录状态下不能再次登录,不做任何处理
}
@Override
public void handleLogout(UserLoginContext context) {
context.setCurrentState(new NotLoggedInState());
// 处理注销成功的逻辑
}
}
// 用户登录上下文类
class UserLoginContext {
private UserLoginState currentState;
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例,初始状态为未登录
public UserLoginContext(UserRepository userRepository) {
this.userRepository = userRepository;
this.currentState = new NotLoggedInState();
}
// 执行用户登录的方法
public void login(String username, String password) {
currentState.handleLogin(this, username, password);
}
// 执行用户注销的方法
public void logout() {
currentState.handleLogout(this);
}
// 设置当前用户登录状态
public void setCurrentState(UserLoginState currentState) {
this.currentState = currentState;
}
// 获取 UserRepository 实例
public UserRepository getUserRepository() {
return userRepository;
}
}
13.3 源码分析
从源码的角度来看,枚举类型和状态模式都可以用于状态管理。枚举类型简单直观,适用于状态较少且状态转换逻辑简单的情况。状态模式则更加灵活,适用于状态较多且状态转换逻辑复杂的情况。在实际应用中,我们可以根据具体的需求选择合适的状态管理方式。
十四、领域层中的日志记录
14.1 日志记录的作用
在领域层中,日志记录可以帮助开发者调试和监控应用程序的运行状态。通过记录重要的业务事件和错误信息,开发者可以及时发现和解决问题,提高应用程序的稳定性和可靠性。
14.2 日志记录的实现方式
在 Java 中,我们可以使用 Android 的 Log
类或第三方日志库(如 Timber
)来实现日志记录。以下是一个使用 Timber
来记录日志的示例:
java
import timber.log.Timber;
// 领域层中的用例类,使用日志记录
class UserRegistrationUseCaseWithLogging {
private final UserRepository userRepository;
// 构造函数,注入 UserRepository 实例
public UserRegistrationUseCaseWithLogging(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 执行用户注册的方法
public void registerUser(User user) {
Timber.d("User registration started: %s", user.getName());
try {
// 将用户对象转换为 UserEntity 对象
UserEntity userEntity = UserMapper.map(user);
// 调用 userRepository 的 createUser 方法创建用户
userRepository.createUser(userEntity);
Timber.d("User registration succeeded: %s", user.getName());
} catch (Exception e) {
Timber.e(e, "User registration failed: %s", user.getName());
}
}
}
14.3 日志级别和配置
在使用日志记录时,需要根据不同的情况设置合适的日志级别,例如 DEBUG
、INFO
、WARN
、ERROR
等。同时,还可以配置日志的输出方式,例如输出到控制台、文件或远程服务器。以下是一个使用 Timber
配置日志级别的示例:
java
import android.app.Application;
import timber.log.Timber;
// 应用程序类,配置 Timber 日志库
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
// 在调试模式下,使用 DebugTree 输出日志到控制台
Timber.plant(new Timber.DebugTree());
} else {
// 在发布模式下,使用自定义的日志树记录日志
Timber.plant(new CrashReportingTree());
}
}
// 自定义日志树,用于在发布模式下记录日志
private static class CrashReportingTree extends Timber.Tree {
@Override
protected void log(int priority, String tag, String message, Throwable t) {
// 在这里可以将日志记录到文件或远程服务器
if (priority == Log.ERROR) {
// 处理错误日志,例如上传到崩溃报告平台
}
}
}
}
14.4 源码分析
从源码的角度来看,日志记录可以帮助开发者更好地理解应用程序的运行状态。使用 Timber
等日志库可以方便地实现日志的记录和管理,同时可以根据不同的环境和需求配置日志级别和输出方式。
十五、领域层的性能优化
15.1 性能优化的重要性
在领域层中,性能优化可以提高应用程序的响应速度和资源利用率,从而提升用户体验。由于领域层负责处理核心业务逻辑,任何性能瓶颈都可能导致应用程序的卡顿和延迟。因此,对领域层进行性能优化是非常必要的。
15.2 性能优化的方法
15.2.1 减少数据库查询次数
在领域层中,频繁的数据库查询会导致性能下降。可以通过批量查询、缓存和预加载等方式来减少数据库查询次数。例如,在获取多个用户信息时,可以使用批量查询语句一次性获取所有用户信息,而不是多次查询。
java
// 批量获取用户信息的方法
public List<User> getUsersByIds(List<Integer> userIds) {
// 构建批量查询语句
StringBuilder query = new StringBuilder("SELECT * FROM users WHERE id IN (");
for (int i = 0; i < userIds.size(); i++) {
if (i > 0) {
query.append(",");
}
query.append("?");
}
query.append(")");
// 执行批量查询
List<UserEntity> userEntities = userRepository.getUsersByQuery(query.toString(), userIds.toArray(new Integer[0]));
// 将用户实体转换为领域模型
return UserMapper.map(userEntities);
}
15.2.2 优化算法复杂度
在领域层中,有些业务逻辑可能涉及到复杂的算法。可以通过优化算法复杂度来提高性能。例如,使用更高效的排序算法、查找算法等。
java
// 优化后的查找用户的方法,使用二分查找算法
public User findUserById(List<User> users, int id) {
int left = 0;
int right = users.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (users.get(mid).getId() == id) {
return users.get(mid);
} else if (users.get(mid).getId() < id) {
left = mid + 1;
} else {
right =
15.2.3 利用 Room 的异步能力(协程实现)
kotlin
// 领域层 Repository 协程实现(关键源码)
class UserRepositoryImpl(
private val db: AppDatabase,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : UserRepository {
// 使用 Room 协程挂起函数(内部自动切换线程)
override suspend fun getUsersByAge(age: Int): List<User> = withContext(ioDispatcher) {
// 直接调用 Room DAO 的协程方法
db.userDao().getUsersByAge(age).map { entity ->
// 实体 -> 领域模型转换(内联优化)
User(
id = entity.id,
name = entity.name,
age = entity.age,
// 复杂字段转换(如时间戳转日期)
birthday = LocalDate.ofEpochDay(entity.birthdayEpoch)
)
}
}
// 批量插入带事务(Room 自动生成事务代码)
override suspend fun insertUsers(users: List<User>) = withContext(ioDispatcher) {
db.runInTransaction {
users.forEach { user ->
db.userDao().insert(UserEntity(
id = user.id,
name = user.name,
age = user.age,
birthdayEpoch = user.birthday.toEpochDay()
))
}
}
}
}
// Room 生成的 DAO 协程方法(反编译代码)
public final class UserDao_Impl implements UserDao {
// 协程挂起函数封装
@Override
public Object getUsersByAge(final int age, final Continuation<? super List<UserEntity>> continuation) {
return SuspendSupport.executeSuspending(__db, () -> {
// 实际 SQL 查询(预编译语句)
final String _sql = "SELECT * FROM users WHERE age = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, age);
// 结果映射(避免反射)
final List<UserEntity> _result = new ArrayList<>();
try (Cursor _cursor = __db.query(_statement)) {
while (_cursor.moveToNext()) {
UserEntity _entity = new UserEntity();
_entity.id = _cursor.getInt(0);
_entity.name = _cursor.getString(1);
_entity.age = _cursor.getInt(2);
_entity.birthdayEpoch = _cursor.getLong(3);
_result.add(_entity);
}
}
return _result;
}, continuation);
}
}
15.2.4 缓存穿透优化(内存 + 磁盘二级缓存)
kotlin
// 领域层缓存管理器(结合 Room 变更监听)
class UserCacheManager(
private val repository: UserRepository,
private val memoryCache: LruCache<Long, User> = LruCache(100)
) {
init {
// 监听 Room 数据库变更(通过 InvalidationTracker)
repository.getDatabase().invalidationTracker.addObserver("users") {
memoryCache.evictAll() // 表数据变更时清空内存缓存
}
}
suspend fun getUser(id: Long): User {
// 内存缓存优先
memoryCache[id]?.let { return it }
// 磁盘查询(Room 协程)
val user = repository.getUser(id)
// 异步写入缓存(不阻塞主线程)
launch(Dispatchers.IO) {
memoryCache.put(id, user)
saveToDiskCache(id, user) // 可选磁盘缓存
}
return user
}
private suspend fun saveToDiskCache(id: Long, user: User) {
// 序列化到磁盘(使用 Room 的 TypeConverter)
val serialized = UserTypeConverter.toByteArray(user)
diskCache.put(id, serialized)
}
}
// Room 类型转换器(支持复杂对象序列化)
@TypeConverter
class UserTypeConverter {
companion object {
fun toByteArray(user: User): ByteArray {
return ObjectOutputStream(ByteArrayOutputStream()).use {
it.writeObject(user)
it.toByteArray()
}
}
fun toUser(byteArray: ByteArray): User {
return ObjectInputStream(ByteArrayInputStream(byteArray)).use {
it.readObject() as User
}
}
}
}
十六、领域层与 Room 的深度集成
16.1 Repository 模式实现(核心源码)
kotlin
// 领域层 Repository 接口
interface UserRepository {
suspend fun getUser(id: Long): User
suspend fun getUsersByAge(age: Int): List<User>
suspend fun insertUsers(users: List<User>)
fun getDatabase(): AppDatabase // 暴露数据库用于监听
}
// Room 实现的 Repository(关键逻辑)
class RoomUserRepository(
private val db: AppDatabase
) : UserRepository {
// 协程安全的 DAO 访问
private val userDao by lazy { db.userDao() }
override suspend fun getUser(id: Long): User {
return userDao.getUser(id).let { entity ->
User(
id = entity.id,
name = entity.name,
age = entity.age,
// 关联查询(Room 自动生成嵌套查询)
addresses = db.addressDao().getByUserId(entity.id).map { addrEntity ->
Address(addrEntity.street, addrEntity.city)
}
)
}
}
// 带事务的批量操作(Room 编译时生成事务代码)
@Transactional
override suspend fun insertUsers(users: List<User>) {
users.forEach { user ->
userDao.insert(UserEntity(
id = user.id,
name = user.name,
age = user.age
))
user.addresses.forEach { addr ->
db.addressDao().insert(AddressEntity(
userId = user.id,
street = addr.street,
city = addr.city
))
}
}
}
override fun getDatabase(): AppDatabase = db
}
// Room 生成的事务代理(反编译代码)
public class RoomUserRepository_TransactionProxy implements UserRepository {
private final UserRepository delegate;
private final RoomDatabase db;
public RoomUserRepository_TransactionProxy(UserRepository delegate, RoomDatabase db) {
this.delegate = delegate;
this.db = db;
}
@Override
@Transactional
public void insertUsers(List<User> users) {
db.beginTransaction();
try {
delegate.insertUsers(users);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
}
16.2 领域事件与 Room 变更监听
kotlin
// 领域事件总线(基于Room的InvalidationTracker)
class DomainEventBus(private val db: AppDatabase) {
private val eventListeners = mutableMapOf<String, MutableList<(Any) -> Unit>>()
init {
// 监听所有表变更
db.invalidationTracker.addObserver { tables ->
tables.forEach { table ->
eventListeners[table]?.forEach { listener ->
listener(DatabaseChangedEvent(table))
}
}
}
}
fun <T> register(table: String, listener: (T) -> Unit) {
eventListeners.getOrPut(table, ::mutableListOf).add(listener as (Any) -> Unit)
}
data class DatabaseChangedEvent(val table: String) : DomainEvent
}
// 领域层监听用户表变更
class UserUseCase(
private val repository: UserRepository,
private val eventBus: DomainEventBus
) {
init {
eventBus.register<UserRepository.DatabaseChangedEvent>("users") { event ->
// 表变更时刷新缓存
userCache.evictAll()
}
}
suspend fun refreshUser(id: Long): User {
// 强制从数据库加载
return repository.getUser(id).also {
userCache.put(id, it)
}
}
}
十七、领域层的单元测试(源码级验证)
17.1 纯领域逻辑测试(无 Room 依赖)
kotlin
// 用例测试(模拟Repository)
class CreateUserUseCaseTest {
private val mockRepository = mock<UserRepository>()
private val useCase = CreateUserUseCase(mockRepository)
@Test
fun `should validate age before insert`() = runTest {
// 准备数据
val user = User(name = "Alice", age = 17)
// 执行用例
val result = useCase.execute(user)
// 验证业务规则
assertEquals(false, result)
verify(mockRepository, never()).insertUsers(any())
}
@Test
fun `should insert user with valid age`() = runTest {
// 准备数据
val user = User(name = "Bob", age = 18)
coEvery { mockRepository.insertUsers(any()) } just Runs
// 执行用例
val result = useCase.execute(user)
// 验证调用
assertEquals(true, result)
coVerify { mockRepository.insertUsers(listOf(user)) }
}
}
// 业务规则测试
class UserBusinessRulesTest {
@Test
fun `isUserAgeValid should return false for underage`() {
val user = User(age = 17)
assertFalse(UserBusinessRules.isUserAgeValid(user))
}
@Test
fun `isUserAgeValid should return true for adult`() {
val user = User(age = 18)
assertTrue(UserBusinessRules.isUserAgeValid(user))
}
}
17.2 集成测试(结合 Room 测试库)
kotlin
// Room 集成测试(使用TestDatabase)
@RunWith(AndroidJUnit4::class)
class UserRepositoryTest {
private lateinit var db: AppDatabase
private lateinit var repository: UserRepository
@Before
fun setup() {
db = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
).allowMainThreadQueries().build()
repository = RoomUserRepository(db)
}
@After
fun teardown() {
db.close()
}
@Test
fun `insert and get user should return correct data`() = runTest {
// 插入数据
val user = User(name = "Charlie", age = 25)
repository.insertUsers(listOf(user))
// 查询验证
val result = repository.getUser(user.id)
assertEquals(user.name, result.name)
assertEquals(user.age, result.age)
}
@Test
fun `transaction should rollback on error`() = runTest {
// 模拟异常
coEvery { db.userDao().insert(any()) } throws IOException("Test")
// 执行事务
assertFailsWith<IOException> {
repository.insertUsers(listOf(User(name = "Error", age = 18)))
}
// 验证数据未插入
assertEquals(0, repository.getUsersByAge(18).size)
}
}
十八、领域层设计模式深度解析
18.1 命令查询职责分离(CQRS)
kotlin
// 查询用例(无副作用)
class GetUserQueryUseCase(
private val repository: UserRepository
) {
suspend fun execute(id: Long): User {
return repository.getUser(id)
}
}
// 命令用例(有副作用)
class UpdateUserCommandUseCase(
private val repository: UserRepository,
private val validator: UserValidator
) {
suspend fun execute(user: User): Result<Unit> {
return try {
validator.validate(user)
repository.updateUser(user)
Result.success(Unit)
} catch (e: ValidationException) {
Result.failure(e)
}
}
}
// 领域验证器(独立于Room)
class UserValidator {
fun validate(user: User) {
if (user.age < 18) {
throw ValidationException("Underage user")
}
if (user.name.isBlank()) {
throw ValidationException("Name cannot be empty")
}
}
}
18.2 策略模式(动态选择数据源)
kotlin
// 数据源策略接口
interface UserDataSource {
suspend fun getUsers(): List<User>
}
// Room 数据源实现
class RoomDataSource(private val repository: UserRepository) : UserDataSource {
override suspend fun getUsers() = repository.getAllUsers()
}
// 内存数据源实现(测试用)
class MemoryDataSource(private val data: List<User>) : UserDataSource {
override suspend fun getUsers() = data
}
// 策略上下文(领域层入口)
class UserDataContext(
private val strategies: Map<DataSourceType, UserDataSource>
) {
suspend fun getUsers(type: DataSourceType): List<User> {
return strategies[type]?.getUsers() ?: throw NoSuchElementException()
}
}
// 使用示例(根据条件选择数据源)
val context = UserDataContext(
mapOf(
DataSourceType.DB to RoomDataSource(repository),
DataSourceType.CACHE to MemoryDataSource(cachedUsers)
)
)
// 在 UseCase 中动态选择
class FlexibleGetUsersUseCase(private val context: UserDataContext) {
suspend fun execute(preferCache: Boolean): List<User> {
return if (preferCache && hasValidCache()) {
context.getUsers(DataSourceType.CACHE)
} else {
context.getUsers(DataSourceType.DB)
}
}
}
十九、领域层与 Room 的边界设计
19.1 实体到领域模型的转换层
kotlin
// 转换层接口(明确边界)
interface UserConverter {
fun toDomain(entity: UserEntity): User
fun toEntity(domain: User): UserEntity
}
// 默认实现(内联转换)
class DefaultUserConverter : UserConverter {
override fun toDomain(entity: UserEntity): User {
return User(
id = entity.id,
name = entity.name,
age = entity.age,
// 复杂类型转换(使用 Room TypeConverter)
preferences = UserPreferencesTypeConverter.fromString(entity.preferencesJson)
)
}
override fun toEntity(domain: User): UserEntity {
return UserEntity(
id = domain.id,
name = domain.name,
age = domain.age,
preferencesJson = UserPreferencesTypeConverter.toString(domain.preferences)
)
}
}
// 在 Repository 中注入使用
class RoomUserRepository(
private val db: AppDatabase,
private val converter: UserConverter = DefaultUserConverter()
) : UserRepository {
override suspend fun getUser(id: Long): User {
return db.userDao().getUser(id).let(converter::toDomain)
}
}
19.2 领域异常封装(清晰的错误边界)
kotlin
// 领域异常基类
open class DomainException(message: String) : Exception(message)
// 业务规则异常
class AgeValidationException(age: Int) : DomainException(
"Age $age is below minimum legal age (18)"
)
// 数据访问异常
class DatabaseOperationException(cause: Throwable) : DomainException(
"Database operation failed", cause
)
// 在 Repository 中封装异常
class RoomUserRepository(...) {
override suspend fun insertUsers(users: List<User>) {
return try {
db.runInTransaction {
users.forEach { user ->
if (user.age < 18) throw AgeValidationException(user.age)
db.userDao().insert(converter.toEntity(user))
}
}
} catch (e: SQLiteException) {
throw DatabaseOperationException(e)
}
}
}
// UseCase 处理异常
class CreateUserUseCase(...) {
suspend fun execute(user: User): Result<Unit> {
return try {
repository.insertUsers(listOf(user))
Result.success(Unit)
} catch (e: DomainException) {
Result.failure(e)
}
}
}
二十、总结:领域层的 Room 最佳实践
20.1 源码级设计原则
- 严格分层:领域层只依赖 Room 接口(如 DAO),不直接使用 Room 实现类
- 数据转换显式化:通过
Converter
类隔离实体与领域模型 - 异步抽象:使用协程统一封装 Room 的异步操作(避免回调地狱)
- 事务内聚:在 Repository 中通过
@Transactional
封装业务事务 - 变更监听:利用 Room 的
InvalidationTracker
实现领域事件
20.2 性能优化清单
优化点 | 实现方式 | 源码位置 |
---|---|---|
批量操作 | runInTransaction + 预编译语句重用 | RoomDatabase.java |
缓存穿透 | 内存缓存 + Room 变更监听 | InvalidationTracker.java |
关联查询 | @Relation + 嵌套对象映射 | 编译生成的*Mapper.java |
线程安全 | 协程调度器(默认 IO 线程) | SuspendSupport.java |
日志监控 | 自定义RoomSQLiteQuery 日志拦截 |