阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 数据访问层的抽象设计

数据访问层的抽象设计

作者:陈川 阅读数:28125人阅读 分类: Node.js

数据访问层的核心作用

数据访问层(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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌