Skip to content

Implementasi Redis sebagai Publish Subscribe

alt text

Selain sebagai sebuah cache manager, redis juga bisa digunkan sebagai Redis Pub/Sub. Publish/Subscribe adalah fitur Redis yang memungkinkan komunikasi real-time antar sistem atau aplikasi seperti kafka yang kita pelajari sebelumnya. Ini digunakan untuk pengiriman pesan secara asyncronous antara publisher (pengirim pesan) dan subscriber (penerima pesan).

Bagaimana Redis Pub/Sub Bekerja:

Publisherakan mngirim pesan ke channel tertentu, kemudian Subscriberakan Mendengarkan (subscribe) channel tertentu dan menerima pesan yang diterbitkan olehpublisher ke channel tersebut. Ketika sebuah pesan dikirimkan oleh publisher ke channel Redis, Redis akan mengirimkan pesan tersebut ke semuasubscriber yang listen channel yang sama.

Penggunaan Redis Pub/Sub:

  • Real-time communication: Digunakan untuk membangun sistem real-time seperti notifikasi, chat aplikasi, atau streaming data.
  • Event-driven architecture: Cocok untuk aplikasi yang menggunakan arsitektur berbasis event, di mana satu layanan dapat menerbitkan event dan layanan lain mendengarkannya.

Perbedaan Redis Pub/Sub dan Redis Cache

Fitur Redis Pub/Sub Redis Cache
Fungsi utama Komunikasi real-time antar sistem (publish/subscribe) Penyimpanan sementara data untuk akses cepat (caching)
Cara kerja Pesan dikirim ke channel dan diterima oleh subscriber Data disimpan dengan kunci dan dapat diambil kembali melalui cache
Penyimpanan data Tidak menyimpan pesan, pesan hanya diteruskan ke subscriber Data disimpan dalam memori untuk diambil kembali dalam waktu tertentu
Contoh penggunaan Chat aplikasi, notifikasi real-time, event-driven systems Menyimpan hasil kueri database untuk mengurangi beban akses
Skalabilitas Terbatas dalam skala besar, lebih cocok untuk skenario kecil Bisa digunakan di aplikasi besar untuk mengurangi latensi

NestJS sebagai Redis Subscribe

Untuk membuatRedis Subscribe kita harus konfigurasi terlebih dahulu seperti pada kafka sebelumnya. Pertama kita buat sebuah file redis.config.ts untuk menyimpan konfigurasi global untuk redis di nestjs.

config/redis.config.ts
import { RedisOptions, Transport } from '@nestjs/microservices';
import { config as dotenv } from 'dotenv';

dotenv();

export const redisConfig: RedisOptions = {
  transport: Transport.REDIS,
  options: {
    host: `${process.env.REDIS_HOST}`,
    port: Number(process.env.REDIS_PORT),
    password: `${process.env.REDIS_PASSWORD}`,
  },
};

Kemudian kita implementasi config tersebut untuk menghubungkan nestjs ke dalam microservice yang menggunakan redis.

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { useContainer } from 'class-validator';
import { kafkaConfig } from './config/kafka.config';
import { MicroserviceOptions } from '@nestjs/microservices';
import { redisConfig } from './config/redis.config';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    cors: true,
  });

  app.enableCors();
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidUnknownValues: true,
      transform: true,
      validateCustomDecorators: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );
  app.connectMicroservice<MicroserviceOptions>(redisConfig);
  app.connectMicroservice<MicroserviceOptions>(kafkaConfig);
  app.startAllMicroservices();
  useContainer(app.select(AppModule), { fallbackOnErrors: true });
  await app.listen(process.env.APP_PORT);
}
bootstrap();

Info

  • app.connectMicroservice: Ini adalah method dari NestJS yang digunakan untuk menghubungkan aplikasi ke sebuah microservice.
  • <MicroserviceOptions>: Ini adalah generic type yang menandakan bahwa kita akan memberikan opsi konfigurasi untuk microservice. Opsi ini harus sesuai dengan interface MicroserviceOptions yang disediakan oleh @nestjs/microservices.
  • redisConfig: Ini adalah objek yang berisi konfigurasi untuk menghubungkan ke Redis. Objek ini biasanya berisi informasi seperti host Redis, port, dan mungkin juga password jika Redis Anda diproteksi dengan password.

Setelah kita menjadikan nest sebagai subscribe, selanjutkan kita buat controller untuk mendengar dan menerima apabila ada message yang dikirim dari publish. Pada contoh ini kita buat channel bernama notifikasi

order.controller.ts
 @MessagePattern("notifikasi")
  async handleOrderCrated(data) {
    const pesan = {
      to: data.id,
      message: `Update berhasil`,
    };
    console.log(pesan);
  }

NestJS sebagai Redis Publisher

Untuk membaut redis sebagai publisher , kita daftarkan redis config tersebut di redis.module.ts

redis.module.ts
import { Global, Module } from '@nestjs/common';
import { RedisService } from './redis.service';
import { CacheModule } from '@nestjs/cache-manager';
import { ClientsModule } from '@nestjs/microservices';
import { redisConfig } from 'src/config/redis.config';

@Global()
@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'LATIHAN_REDIS_PUBSUB',
        ...redisConfig,
      },
    ]),

    CacheModule.registerAsync({
      isGlobal: true,
      useFactory: async () => {
        const { config } = await import('../config/cache.config');
        return config;
      },
    }),
  ],
  providers: [RedisService],
  exports: [RedisService],
})
export class RedisModule {}
redis.service.ts
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Cache } from 'cache-manager';

@Injectable()
export class RedisService {
  @Inject(CACHE_MANAGER) private cacheManager: Cache;
  @Inject('LATIHAN_REDIS_PUBSUB') private client: ClientProxy;

  async setCacheKey({
    key: key,
    data: data,
    ttl = 60,
  }: {
    key: string | number;
    data: any;
    ttl?: number;
  }) {
    return await this.cacheManager.set(`${key}`, data, ttl);
  }

  async getCacheKey(key: string) {
    return await this.cacheManager.get(`${key}`);
  }

  async deleteCacheKey(key: string) {
    return await this.cacheManager.del(`${key}`);
  }

  async sendNotification(channel: string, data: any) {
    this.client.emit(channel, { ...data });
    return data;
  }
}
order.service.ts
import {
  HttpException,
  HttpStatus,
  Inject,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import BaseResponse from 'src/utils/response/base.response';
import { Between, Like, Repository } from 'typeorm';
import { Order } from './order.entity';
import { ResponsePagination, ResponseSuccess } from 'src/interface/response';
import { CreateOrderDto, UpdateOrderDto, findAllOrderDto } from './order.dto';
import { REQUEST } from '@nestjs/core';
import { Workbook } from 'exceljs';
import { Response } from 'express';
import { KafkaService } from 'src/kafka/kafka.service';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { RedisService } from 'src/redis/redis.service';

@Injectable()
export class OrderService extends BaseResponse {
  constructor(
    @InjectRepository(Order)
    private readonly orderRepository: Repository<Order>,
    @Inject(REQUEST) private req: any,
    private readonly kafkaService: KafkaService,
    private readonly redisService: RedisService,
  ) {
    super();
  }


  ...

  async updateOrder(
    id: number,
    payload: UpdateOrderDto,
  ): Promise<ResponseSuccess> {
    const check = await this.orderRepository.findOne({
      where: {
        id: id,
      },
    });

    if (!check) {
      throw new HttpException('Data tidak ditemukan', HttpStatus.NOT_FOUND);
    }

    payload.order_detail &&
      payload.order_detail.forEach((item) => {
        item.created_by = this.req.user.id;
      });

    const order = await this.orderRepository.save({ ...payload, id: id });

    this.redisService.deleteCacheKey(`order_${id}`);
    const hasil = await this.redisService.sendNotification('notifikasi', {
      id: id,
    });

    return this._success('OK', order;
  }


}

Testing pada Postman

alt text alt text