数据访问层的抽象设计
数据访问层的核心作用
数据访问层(DAL)在Koa2应用中承担着与数据源交互的职责,它将业务逻辑与具体的数据存储技术解耦。良好的抽象设计能让应用在不同数据库间无缝切换,同时保持业务代码的稳定性。当从MySQL迁移到MongoDB时,只需修改DAL实现而无需改动服务层代码。
基础抽象模式设计
定义通用数据操作接口是抽象的第一步。以下示例展示了一个基础Repository模式在TypeScript中的实现:
interface IRepository<T> {
create(entity: T): Promise<T>;
findById(id: string): Promise<T | null>;
update(id: string, updates: Partial<T>): Promise<boolean>;
delete(id: string): Promise<boolean>;
}
class UserRepository implements IRepository<User> {
async create(user: User): Promise<User> {
// 具体数据库实现
}
async findById(id: string): Promise<User | null> {
// 具体数据库实现
}
}
这种模式强制所有具体实现遵守相同契约,业务层只需依赖IRepository
接口。当需要切换数据源时,只需提供新的实现类:
class MongoUserRepository implements IRepository<User> {
// MongoDB特定实现
}
class MySQLUserRepository implements IRepository<User> {
// MySQL特定实现
}
查询规范的抽象处理
复杂查询场景需要更精细的抽象。Specification模式可以封装查询条件:
interface ISpecification<T> {
isSatisfiedBy(candidate: T): boolean;
toQuery(): object;
}
class ActiveUsersSpec implements ISpecification<User> {
toQuery() {
return { status: 'active' };
}
}
class UserRepository {
async query(spec: ISpecification<User>): Promise<User[]> {
const query = spec.toQuery();
// 将规范转换为具体数据库查询
}
}
这种设计允许业务层构建复杂的查询逻辑,而不需要了解底层数据库语法。同样的规范可以适配不同数据库:
// MongoDB实现
class MongoActiveUsersSpec extends ActiveUsersSpec {
toQuery() {
return { $and: [{ status: 'active' }] };
}
}
// SQL实现
class SQLActiveUsersSpec extends ActiveUsersSpec {
toQuery() {
return 'status = "active"';
}
}
事务管理的统一接口
跨数据源的事务处理需要特殊抽象。Unit of Work模式可以统一事务管理:
class UnitOfWork {
private operations: Array<() => Promise<void>> = [];
register(operation: () => Promise<void>) {
this.operations.push(operation);
}
async commit() {
// 开始事务
try {
for (const op of this.operations) {
await op();
}
// 提交事务
} catch (error) {
// 回滚事务
throw error;
}
}
}
// 使用示例
const uow = new UnitOfWork();
uow.register(() => userRepo.update(user1));
uow.register(() => orderRepo.create(order));
await uow.commit();
连接池的抽象配置
数据库连接管理也需要抽象层。工厂模式适合处理多环境配置:
interface IDatabaseConfig {
host: string;
port: number;
poolSize: number;
}
abstract class DatabaseFactory {
abstract createConnection(config: IDatabaseConfig): Promise<Connection>;
static getFactory(dbType: string): DatabaseFactory {
switch(dbType) {
case 'mysql': return new MySQLFactory();
case 'mongodb': return new MongoFactory();
default: throw new Error('Unsupported database');
}
}
}
class MySQLFactory extends DatabaseFactory {
async createConnection(config: IDatabaseConfig) {
// 具体MySQL连接逻辑
}
}
性能监控的抽象集成
数据访问性能监控应该通过抽象接口实现:
interface IQueryMonitor {
onQueryStart(query: string): void;
onQueryEnd(duration: number): void;
onError(error: Error): void;
}
class PerformanceMonitor implements IQueryMonitor {
onQueryStart(query: string) {
console.time(`query-${query}`);
}
onQueryEnd(duration: number) {
console.timeEnd(`query-${query}`);
}
}
// 在Repository中集成
class MonitoredRepository {
constructor(private monitor: IQueryMonitor) {}
async query(sql: string) {
this.monitor.onQueryStart(sql);
try {
const result = await db.query(sql);
this.monitor.onQueryEnd(/* duration */);
return result;
} catch (err) {
this.monitor.onError(err);
throw err;
}
}
}
多数据源的路由策略
大型应用可能需要动态数据源路由。抽象路由策略可以基于业务规则选择数据源:
class DataSourceRouter {
private readonly strategies: Map<string, IRepository<any>>;
constructor() {
this.strategies = new Map();
}
registerStrategy(key: string, strategy: IRepository<any>) {
this.strategies.set(key, strategy);
}
getStrategy(context: RouterContext): IRepository<any> {
// 基于请求头、用户身份等选择策略
if (context.headers['x-use-replica'] === 'true') {
return this.strategies.get('replica');
}
return this.strategies.get('primary');
}
}
// Koa中间件集成
app.use(async (ctx, next) => {
const repo = router.getStrategy(ctx);
ctx.state.userRepo = repo;
await next();
});
缓存层的透明集成
缓存机制应该对业务代码透明。代理模式可以优雅实现:
class CachedUserRepository implements IRepository<User> {
constructor(
private origin: IRepository<User>,
private cache: CacheStore
) {}
async findById(id: string): Promise<User | null> {
const cacheKey = `user:${id}`;
const cached = await this.cache.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await this.origin.findById(id);
if (user) {
await this.cache.set(cacheKey, JSON.stringify(user), 3600);
}
return user;
}
}
// 使用方式
const realRepo = new UserRepository();
const cachedRepo = new CachedUserRepository(realRepo, redisCache);
批量操作的优化抽象
批量数据处理需要特殊接口设计:
interface IBatchRepository<T> {
createBatch(entities: T[]): Promise<number>;
updateBatch(filter: Partial<T>, updates: Partial<T>): Promise<number>;
// 流式处理
stream(filter: object): AsyncIterable<T>;
}
class UserBatchRepository implements IBatchRepository<User> {
async createBatch(users: User[]): Promise<number> {
// 使用bulk insert优化
}
async *stream(filter: object): AsyncIterable<User> {
// 实现流式读取
}
}
异常处理的统一范式
数据访问异常应该转换为领域异常:
class DataAccessException extends Error {
constructor(
public readonly originalError: Error,
public readonly context: Record<string, any>
) {
super('Data access failed');
}
}
class OptimisticLockException extends DataAccessException {
constructor(public readonly versionConflict: boolean) {
super(new Error('Version conflict'), { versionConflict });
}
}
// 在Repository中转换异常
try {
await db.query('...');
} catch (err) {
if (err.code === 'ER_DUP_ENTRY') {
throw new DuplicateKeyException();
}
throw new DataAccessException(err, { query: '...' });
}
测试专用的内存实现
为单元测试提供内存实现:
class InMemoryRepository<T extends { id: string }> implements IRepository<T> {
private store = new Map<string, T>();
async create(entity: T): Promise<T> {
this.store.set(entity.id, entity);
return entity;
}
async findById(id: string): Promise<T | null> {
return this.store.get(id) || null;
}
}
// 测试用例示例
describe('UserService', () => {
const repo = new InMemoryRepository<User>();
const service = new UserService(repo);
it('should create user', async () => {
const user = await service.createUser({ name: 'Test' });
expect(user.id).toBeDefined();
});
});
领域事件的发布集成
数据变更时发布领域事件:
class EventPublisherRepository<T extends Entity> implements IRepository<T> {
constructor(
private origin: IRepository<T>,
private eventBus: EventBus
) {}
async create(entity: T): Promise<T> {
const result = await this.origin.create(entity);
await this.eventBus.publish('UserCreated', result);
return result;
}
}
// 使用装饰器模式组合
const repo = new EventPublisherRepository(
new CachedUserRepository(
new UserRepository(),
redisCache
),
eventBus
);
数据模型与领域模型的转换
持久化模型与领域模型的转换策略:
class UserMapper {
static toDomain(persistence: PersistenceUser): User {
return new User({
id: persistence._id,
email: persistence.email_address,
// 其他字段转换
});
}
static toPersistence(domain: User): PersistenceUser {
return {
_id: domain.id,
email_address: domain.email,
// 其他字段转换
};
}
}
// 在Repository中使用
class UserRepository {
async findById(id: string): Promise<User> {
const data = await db.collection('users').findOne({ _id: id });
return UserMapper.toDomain(data);
}
}
查询构建器的动态组合
动态查询条件的构建抽象:
class QueryBuilder {
private conditions: any[] = [];
where(field: string, op: string, value: any): this {
this.conditions.push({ field, op, value });
return this;
}
build(): object {
// 转换为具体数据库查询条件
return this.conditions.reduce((query, cond) => {
query[cond.field] = { [cond.op]: cond.value };
return query;
}, {});
}
}
// 使用示例
const query = new QueryBuilder()
.where('age', '$gt', 18)
.where('status', '$eq', 'active')
.build();
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:服务层与业务逻辑封装
下一篇:DTO 与数据格式转换