0
0
Lập trình
Flame Kris
Flame Krisbacodekiller

Mang AOP kiểu Spring vào NestJS với nestjs-saop

Đăng vào 8 tháng trước

• 8 phút đọc

Chủ đề:

KungFuTech

Giới thiệu

Nếu bạn đã làm việc với Spring Framework, chắc hẳn bạn đã từng trải nghiệm sức mạnh của lập trình hướng khía cạnh (AOP). AOP giúp bạn tách biệt các mối quan tâm chéo như ghi log, caching, bảo mật và quản lý giao dịch ra khỏi logic ứng dụng, giúp mã nguồn trở nên dễ bảo trì và đọc hiểu hơn.

Vậy nếu bạn có thể mang trải nghiệm AOP thanh lịch đó vào các ứng dụng NestJS của mình? Hãy cùng khám phá nestjs-saop – một thư viện mang AOP kiểu Spring vào hệ sinh thái NestJS với hỗ trợ TypeScript hoàn chỉnh.

nestjs-saop là gì?

nestjs-saop là thư viện AOP toàn diện cho NestJS, cung cấp:

  • Tất cả các loại lời khuyên AOP: Tất cả 5 loại lời khuyên kiểu Spring (Around, Before, After, AfterReturning, AfterThrowing)
  • Hỗ trợ TypeScript đầy đủ: Đảm bảo an toàn kiểu với generics và interfaces
  • Tích hợp liền mạch với NestJS: Hoạt động hoàn hảo với hệ thống module của NestJS
  • Mô hình Decorator: API dựa trên decorator quen thuộc và dễ tiếp cận
  • Cấu hình linh hoạt: Tùy chỉnh cao với hỗ trợ các tùy chọn tùy chỉnh

Thiết lập nhanh

Cài đặt gói:

Copy
npm install nestjs-saop

Đăng ký module AOP trong ứng dụng của bạn:

typescript Copy
import { AOPModule } from 'nestjs-saop';

@Module({
  imports: [
    AOPModule.forRoot(),
  ],
})
export class AppModule {}

Tạo Decorator AOP đầu tiên của bạn

Hãy cùng tạo một decorator để ghi log, thể hiện tất cả các loại lời khuyên AOP:

typescript Copy
import { AOPDecorator, Aspect } from 'nestjs-saop';

@Aspect()
export class LoggingDecorator extends AOPDecorator {
  around({ method, options }) {
    return (...args: any[]) => {
      console.log('🔄 Around: Trước khi thực thi phương thức');
      const result = method.apply(this, args);
      console.log('🔄 Around: Sau khi thực thi phương thức');
      return result;
    };
  }

  before({ method, options }) {
    return (...args: any[]) => {
      console.log('▶️ Before: Phương thức được gọi với', args);
    };
  }

  after({ method, options }) {
    return (...args: any[]) => {
      console.log('⏹️ After: Phương thức đã hoàn thành');
    };
  }

  afterReturning({ method, options, result }) {
    return (...args: any[]) => {
      console.log('✅ AfterReturning: Phương thức trả về', result);
    };
  }

  afterThrowing({ method, options, error }) {
    return (...args: any[]) => {
      console.error('❌ AfterThrowing: Phương thức ném ra lỗi', error.message);
    };
  }
}

Đăng ký decorator của bạn như một provider:

typescript Copy
@Module({
  providers: [LoggingDecorator],
})
export class AppModule {}

Ví dụ thực tế

1. Decorator Caching thông minh

typescript Copy
@Aspect()
export class CachingDecorator extends AOPDecorator {
  private cache = new Map();

  around({ method, options }) {
    return (...args: any[]) => {
      const key = `${method.name}:${JSON.stringify(args)}`;

      // Cache hit
      if (this.cache.has(key)) {
        console.log('💡 Cache hit!');
        return this.cache.get(key);
      }

      // Cache miss - thực thi phương thức
      console.log('💭 Cache miss, đang thực thi phương thức...');
      const result = method.apply(this, args);

      // Lưu vào cache với TTL
      this.cache.set(key, result);
      if (options.ttl) {
        setTimeout(() => this.cache.delete(key), options.ttl);
      }

      return result;
    };
  }
}

// Sử dụng
@Injectable()
export class UserService {
  @CachingDecorator.around({ ttl: 300000 }) // 5 phút
  async getUserById(id: string): Promise<User> {
    return await this.userRepository.findById(id);
  }
}

2. Giám sát hiệu suất

typescript Copy
@Aspect()
export class PerformanceDecorator extends AOPDecorator {
  around({ method, options }) {
    return (...args: any[]) => {
      const start = Date.now();

      try {
        const result = method.apply(this, args);
        const duration = Date.now() - start;

        if (duration > options.threshold) {
          console.warn(`⚠️ Phương thức chậm phát hiện: ${method.name} (${duration}ms)`);
        }

        return result;
      } catch (error) {
        const duration = Date.now() - start;
        console.error(`💥 Phương thức thất bại: ${method.name} (${duration}ms)`);
        throw error;
      }
    };
  }
}

// Sử dụng
@Injectable()
export class DataService {
  @PerformanceDecorator.around({ threshold: 1000 })
  async processLargeDataset(data: any[]): Promise<ProcessedData> {
    // Hoạt động tốn kém của bạn ở đây
    return await this.complexProcessing(data);
  }
}

3. Xử lý lỗi với logic thử lại

typescript Copy
@Aspect()
export class RetryDecorator extends AOPDecorator {
  afterThrowing({ method, options, error }) {
    return (...args: any[]) => {
      console.error(`🔄 Phương thức ${method.name} thất bại:`, error.message);

      if (options.retryCount < options.maxRetries) {
        console.log(`🔄 Đang thử lại... (${options.retryCount + 1}/${options.maxRetries})`);

        // Thực hiện exponential backoff
        const delay = Math.pow(2, options.retryCount) * 1000;
        setTimeout(() => {
          // Logic thử lại ở đây
          method.apply(this, args);
        }, delay);
      }
    };
  }
}

// Sử dụng
@Injectable()
export class ApiService {
  @RetryDecorator.afterThrowing({ maxRetries: 3, retryCount: 0 })
  async callExternalAPI(endpoint: string): Promise<any> {
    const response = await fetch(endpoint);
    if (!response.ok) {
      throw new Error(`API call failed: ${response.status}`);
    }
    return response.json();
  }
}

Sức mạnh của TypeScript 💪

Một trong những tính năng mạnh mẽ nhất của nestjs-saop chính là hỗ trợ TypeScript hoàn chỉnh. Định nghĩa các loại tùy chọn tùy chỉnh để cải thiện trải nghiệm lập trình viên:

typescript Copy
interface LoggingOptions {
  level: 'debug' | 'info' | 'warn' | 'error';
  includeTimestamp: boolean;
  logArgs: boolean;
}

@Aspect()
export class TypedLoggingDecorator extends AOPDecorator<LoggingOptions> {
  before({ method, options }) {
    return (...args: any[]) => {
      const timestamp = options.includeTimestamp 
        ? `[${new Date().toISOString()}] ` 
        : '';

      const argsLog = options.logArgs 
        ? ` với args: ${JSON.stringify(args)}` 
        : '';

      console.log(
        `${timestamp}${options.level.toUpperCase()}: ${method.name}${argsLog}`
      );
    };
  }
}

// Sử dụng với an toàn kiểu hoàn toàn
@Injectable()
export class UserService {
  @TypedLoggingDecorator.before({
    level: 'info',           // ✅ An toàn kiểu
    includeTimestamp: true,  // ✅ An toàn kiểu  
    logArgs: false          // ✅ An toàn kiểu
  })
  async createUser(userData: CreateUserDto): Promise<User> {
    return await this.userRepository.create(userData);
  }
}

Kết hợp nhiều Decorators

Bạn có thể xếp chồng nhiều decorators trên một phương thức để tạo ra những sự kết hợp mạnh mẽ:

typescript Copy
@Injectable()
export class PaymentService {
  @LoggingDecorator.before({ level: 'info', logArgs: true })
  @PerformanceDecorator.around({ threshold: 2000 })
  @CachingDecorator.around({ ttl: 60000 })
  @RetryDecorator.afterThrowing({ maxRetries: 3 })
  async processPayment(paymentData: PaymentDto): Promise<PaymentResult> {
    // Phương thức này được cải tiến với:
    // 1. Ghi log trước khi thực thi
    // 2. Giám sát hiệu suất 
    // 3. Khả năng caching
    // 4. Tự động thử lại khi thất bại

    return await this.paymentGateway.process(paymentData);
  }
}

Thứ tự thực thi AOP

Các loại lời khuyên thực thi theo thứ tự cụ thể này:

  1. 🔄 Around (trước khi phương thức thực thi)
  2. ▶️ Before
  3. Thực thi phương thức
  4. ⏹️ After
  5. ✅ AfterReturning HOẶC ❌ AfterThrowing
  6. 🔄 Around (sau khi phương thức thực thi)

Kết luận

Thư viện nestjs-saop mang sức mạnh và sự thanh lịch của AOP kiểu Spring vào hệ sinh thái NestJS. Dù bạn cần ghi log, caching, giám sát hiệu suất hay xử lý lỗi, thư viện này giúp bạn triển khai các mối quan tâm chéo một cách sạch sẽ và hiệu quả.

Sự kết hợp giữa cú pháp kiểu Spring quen thuộc, hỗ trợ TypeScript hoàn chỉnh và tích hợp liền mạch với NestJS khiến đây là lựa chọn hoàn hảo để xây dựng các ứng dụng doanh nghiệp dễ bảo trì.

Hãy thử nghiệm nó trong dự án NestJS tiếp theo của bạn – mã nguồn của bạn sẽ cảm ơn bạn! 🚀


📚 Tài nguyên:

  • Kho lưu trữ GitHub
  • Gói npm
  • Tài liệu hướng dẫn

Bạn đã có kinh nghiệm gì về AOP trong phát triển backend chưa? Bạn đã thử triển khai các mối quan tâm chéo trong NestJS chưa? Hãy chia sẻ suy nghĩ của bạn trong phần bình luận bên dưới! 💬

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào