You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

NestJS项目结构咨询:Shop与Orders模块下Shop A、Shop B的实现方案探讨

How to Implement ShopA & ShopB in NestJS (Without Inheriting Modules)

Great question! In NestJS, inheriting module classes is almost never the right approach—modules are dependency injection containers, and subclassing them leads to messy dependency trees and unexpected behavior. Let's break down the proper way to implement ShopA and ShopB based on your existing Shop and Orders modules.

1. Model Layer: Use Entity Inheritance (Not Module Inheritance)

First, your ShopA and ShopB should extend the base Shop entity (not the module). This makes sense because they're specialized versions of a Shop, sharing core properties but adding unique features.

Example with TypeORM (most common for NestJS):

Base Shop Entity

// src/shop/entities/shop.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, TableInheritance } from 'typeorm';

// Use single-table inheritance to store all shops in one table (or use class-table inheritance if you prefer separate tables)
@Entity()
@TableInheritance({ column: { type: 'varchar', name: 'shop_type' } })
export class Shop {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  address: string;
}

Specialized ShopA Entity

// src/shop/entities/shop-a.entity.ts
import { Entity, ChildEntity, Column } from 'typeorm';
import { Shop } from './shop.entity';

@ChildEntity('A')
export class ShopA extends Shop {
  // ShopA-specific fields
  @Column()
  loyaltyProgramId: string;

  @Column({ default: true })
  acceptsDigitalCoupons: boolean;
}

Specialized ShopB Entity

// src/shop/entities/shop-b.entity.ts
import { Entity, ChildEntity, Column } from 'typeorm';
import { Shop } from './shop.entity';

@ChildEntity('B')
export class ShopB extends Shop {
  // ShopB-specific fields
  @Column()
  bulkDiscountThreshold: number;

  @Column()
  wholesaleLicenseNumber: string;
}

2. Service Layer: Extend or Compose Base Shop Services

Instead of inheriting the ShopModule, create specialized services for ShopA and ShopB that either:

  • Extend the base ShopService (reuse core logic), or
  • Depend on the base ShopService (composition over inheritance, if you need more flexibility)

Example: Extending ShopService

Base ShopService

// src/shop/services/shop.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Shop } from '../entities/shop.entity';

@Injectable()
export class ShopService {
  constructor(
    @InjectRepository(Shop)
    protected shopRepository: Repository<Shop>,
  ) {}

  async findAll(): Promise<Shop[]> {
    return this.shopRepository.find();
  }

  async findOne(id: number): Promise<Shop> {
    return this.shopRepository.findOneBy({ id });
  }
}

ShopAService (Extends Base Service)

// src/shop/services/shop-a.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ShopA } from '../entities/shop-a.entity';
import { ShopService } from './shop.service';

@Injectable()
export class ShopAService extends ShopService implements OnModuleInit {
  constructor(
    @InjectRepository(ShopA)
    private shopARepository: Repository<ShopA>,
  ) {
    // Pass the ShopA repo to the parent service
    super(shopARepository);
  }

  // ShopA-specific method
  async findShopsWithLoyaltyProgram(): Promise<ShopA[]> {
    return this.shopARepository.find({
      where: { loyaltyProgramId: Not(IsNull()) },
    });
  }

  // Initialize a default ShopA on app start (answer to your initialization question)
  async initializeDefaultShopA(): Promise<ShopA> {
    const existingDefault = await this.shopARepository.findOneBy({ name: 'Default Shop A' });
    if (existingDefault) return existingDefault;

    const defaultShop = this.shopARepository.create({
      name: 'Default Shop A',
      address: '123 Main St',
      loyaltyProgramId: 'LP-001',
      acceptsDigitalCoupons: true,
    });
    return this.shopARepository.save(defaultShop);
  }

  // Auto-run initialization when the module loads
  async onModuleInit() {
    await this.initializeDefaultShopA();
    console.log('Default ShopA initialized successfully');
  }
}

3. Module Setup: Register Entities & Services in ShopModule

Update your existing ShopModule to include the specialized entities and services. No need for separate modules unless your ShopA/ShopB logic becomes extremely complex.

// src/shop/shop.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Shop, ShopA, ShopB } from './entities';
import { ShopService, ShopAService, ShopBService } from './services';

@Module({
  imports: [TypeOrmModule.forFeature([Shop, ShopA, ShopB])],
  providers: [ShopService, ShopAService, ShopBService],
  exports: [ShopService, ShopAService, ShopBService], // Export if Orders module needs access
})
export class ShopModule {}

4. Integrate with Orders Module

Since you have CustomerOrder and CompanyOrder, inject the specialized shop services into your OrderService to handle order-shop associations:

// src/orders/services/order.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CustomerOrder } from '../entities/customer-order.entity';
import { ShopAService } from '../../shop/services/shop-a.service';

@Injectable()
export class OrderService {
  constructor(
    @InjectRepository(CustomerOrder)
    private customerOrderRepository: Repository<CustomerOrder>,
    private shopAService: ShopAService,
  ) {}

  async createCustomerOrderForShopA(shopId: number, orderData: any): Promise<CustomerOrder> {
    const shop = await this.shopAService.findOne(shopId);
    if (!shop) throw new NotFoundException('ShopA not found');

    const order = this.customerOrderRepository.create({
      ...orderData,
      shop, // Associate order with the ShopA
    });
    return this.customerOrderRepository.save(order);
  }
}

Key Takeaways

  • Never inherit NestJS modules: Modules are DI containers, not classes meant for subclassing. This avoids dependency conflicts and keeps your codebase clean.
  • Use entity inheritance for model specialization: This aligns with your domain logic (ShopA/ShopB are types of Shop).
  • Extend services to reuse core logic: Keep shared Shop functionality in the base ShopService, then add unique features in ShopAService/ShopBService.
  • Initialize via lifecycle hooks: Use OnModuleInit or OnApplicationBootstrap to auto-create default shops when your app starts, or call the initialization method manually in main.ts.

内容的提问来源于stack exchange,提问作者Cecily Miller

火山引擎 最新活动