Jitrak Blog

Redis cache decorator for Hexagonal architecture in NestJS

Redis cache decorator for Hexagonal architecture in NestJS

31 Jan 2025 05:30

Written by: Yosapol Jitrak

Redis cache decorator for Hexagonal architecture in NestJS
Tags:

NestJS

Redis

Hexagonal Architecture

ล่าสุด ผมได้มีโอกาสไปพูดในงาน JavaScript Bangkok 2.0.0 ในหัวข้อ Redis cache decorator for Hexagonal architecture in NestJS แต่ในงานมีเวลาจำกัด และจอมีปัญหา เลยคิดว่าจะมาเขียนเล่าใน Blog อีกครั้ง เพื่อให้คนที่สนใจสามารถอ่านเพิ่มเติมได้

ปัญหา

เราจะมาเล่าถึงปัญหากันก่อน ปัจจุบันงานที่ทำผมทำอยู๋ใช้ MongoDB Atlas และทาง Business อยากจะลด Cost ครับ ซึ่งราคาจะเป็นตามรูปด้านล่างนี้ครับ

MongoDB Atlas Pricing

แน่นอนปัญหานี้เราน่าจะใช้ Cache มาช่วย เพื่อลด Hit ของ Database ครับ ปัจจุบันก็คงจะเลือกใช้เป็น Redis ครับ

Redis Logo

Project ที่ผมทำอยู่เป็น NestJS และเป็น Hexagonal Architecture ครับ ซึ่งการทำ Cache สามารถใช้ Cache Manager ของ NestJS ได้เลย แต่ผมก็ติดปัญหาอยู่ตรงเมื่อเราต้องการทำ Invalidate cache ทันทีเมื่อมีการ Update ข้อมูลใหม่ ๆ ใน Database ครับ ซึ่ง Cache Manager ของ NestJS ก็ยังไม่มีตรงส่วนนี้ เราสามารถทำได้เพียงแค่ใช้ Time To Live (TTL) เท่านั้น ซึ่งหมายความว่ายังไม่ตอบโจทย์การใช้งานของผม

NestJS Cache Manager Credit: NestJS Cache

Decorator of TypeScript

ก่อนจะไปถึงวิธีแก้ปัญหา ผมขอเล่าถึง Decorator ใน TypeScript ก่อน เพราะจะเป็นสิ่งที่ช่วยเราในการแก้ไขปัญหา ใน TypeScript เรามีสิ่งที่เรียกว่า Decorator อยู่ ถ้าเปรียบเทียบให้เห็นภาพ ภาษาอื่น และ Framework อื่น ๆ จะมีสิ่งที่คล้าย ๆ กันอยู่ครับ เรามาลองดูกันว่ามีตัวไหนบ้าง

  1. Annotation in Java ยกตัวอย่างที่หลาย ๆ คนน่าจะเคยเจอกันมาในภาษา Java และใช้ Framework Spring Boot น่าจะคุ้นเคยกันดีกับ Annotation ครับ

Annotations Spring Boot Credit: Spring Annotations Cheat Sheet

  1. Attribute in C# หลายคนที่เคยเขียน C# แล้วใช้ NUnit เขียน Test ก็น่าจะเคยเห็น Attribute ผ่านหน้าผ่านตากันมาบ้างครับ

NUnit Attribute Credit: NUnit Attribute

คราวนี้เราลองมาดูตัวอย่าง Decorator ของ NestJS กันครับ

NestJS Decorator 1 NestJS Decorator 2 NestJS Decorator 3 NestJS Decorator 4

Hexagonal Architecture

ในบทความนี้คงไม่ได้เล่าลงลึกมากครับ หลัก ๆ แล้วตัว Hexagonal Architecture นั้นจะแบ่ง Layer เป็นชั้น ๆ โดยจะทำให้ส่วนที่ผูกติดกับ Framework รวมถึงภายนอกอย่าง Database หรือ API นั้นแยกออกจากส่วนที่เป็น Business Logic ครับ ซึ่งจะเพิ่มความยืดหยุ่นกับ Code ของเราสามารถเปลี่ยนแปลงไปใช้ Framework และ Database อื่นได้ง่ายขึ้น รวมถึงสามารถทำ Unit Test ของ Business Logic ได้ง่ายขึ้นมากครับ โดยภายนอกและภายใน Application จะคุยกันผ่าน Port ครับ โดย Port นั้นจะเป็นเพียงแค่ Interface หลังจากนั้นเราค่อยเอาตัวที่จะเชื่อมต่อมา Implement จริงเป็น Concrete class อีกทีนึงครับ

Hexagonal Architecture Credit: Hexagonal Architecture, there are always two sides to every story

Explicit Architecture Credit: DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together

ยกตัวอย่างตามรูปด้านล่างนี้ครับ

Hexagonal flow

ลองมาดู Code ตัวอย่างกันครับ

example-hexagonal-code-01

example-hexagonal-code-02

example-hexagonal-code-03

example-hexagonal-code-04

แก้ปัญหา

หลังจากปูพื้นกันมาแล้ว เรากลับมาเข้าเรื่องปัญหาของเราดีกว่าครับ เราจะแก้ปัญหากันอย่างไรได้บ้าง

First Solution

First solution cod

Second Solution

Second solution code

oh-my-head

think-meme คราวนี้เราลองมาใช้ความคิดต่ออีกหน่อย เห้ยจริง ๆ เราใช้ Decorator ของ NestJS เต็มบ้านเต็มเมืองใน Code ของเราอยู่แล้วนี่นา คิดออกมาได้เป็นไอเดียสุดท้ายครับ

Firnal Solution

Decorator

กลับมาดูกันว่า Decorator ของ Method เราทำยังไงกับมันได้ครับ

Decorator 01 จะเห็นว่าเราสามารทำอะไรก่อน และหลัง Method ของเราได้ครับ

ซึ่งมีคนคิดคล้ายเรา ๆ เลย ลองดูตัวอย่างด้านล่างนี้ครับ

Memoize Decorator จะเห็นว่าเราเก็บ Original method ไว้ และสุดท้ายคืน descriptor ตัวเดิมกลับออกไป

คราวนี้ลองดู Implement จริงครับ Memoize Decorator Credit: How To Create a Custom Typescript Decorator

คราวนี้ลองมาดูไอเดียของเรากันครับ

Redis Cache for Repository

Redis Cache for Repository

Invalidate Redis Cache for Repository

Invalidate Redis Cache for Repository

หลายคนมาถึงตรงนี้แล้วอาจจะงงว่า Combination key คืออะไร ลองดูตัวอย่างด้านล่างนี้ครับ

Demo combination keys

สรุปก็คือความเป็นไปได้ของ Cache key ของเราทั้งหมดกับ Repository นั้นครับ

Demo code

Demo cache for repository with items mongo repository 1

จะเห็นว่า Code เราเวลาจะต้องการจะทำ Cache ก็แค่แปะ CacheForRepository decorator เข้าไปใน Method ที่เราต้องการจะทำ Cache ได้เลยครับ โดยตัวอย่างนี้ผมให้ Focus แค่ส่วน Cache ก่อนนะครับ โดยอันนี้ Key ที่จะทำการ Cache คือ items:all นั้นเอง

คราวนี้ลองมาดูตัวอย่าง Cache อื่น ๆ กันบ้างครับ

Demo cache for repository with items mongo repository 2

ตัวอย่างนี้จะเป็น Key items:{id:?} เราสามารถแทนค่า ? ได้จาก itemId ที่ส่งเข้ามาใน Method นี้ครับ

ลองไปดูตัวอย่างถัดไปกันครับ

Demo cache for repository with items mongo repository 3

ต่อมาเรามาดูส่วนของ Invalidate Cache กันครับ

Invalidate cache for repository with items mongo repository 1

คราวนี้ลองมาดูตัว Decorator ที่เราเขียนกันครับ

ตัว Cache Decorator จะมีหน้าตาตาม Code ด้านล่างนี้ครับ Cache Decorator

ตัว Invalidate Decorator จะมีหน้าตาตาม Code ด้านล่างนี้ครับ Invalidate Cache Decorator

สำหรับตัวอย่างนี้ผมไม่ได้เล่าทั้งหมดครับ แต่จะเป็น Point ที่สำคัญครับ จะมีตัวอย่างทั้งหมดใน Github ครับ
GitHub: redis-cache-decorator-for-hexagonal-architecture-in-nestjs

Slide: Redis cache decorator for hexagonal architecture in NestJS

จากทั้งหมดนี้จะเห็นว่าเราใช้เวลาคิดหน่อย แต่จะช่วยลดงาน และความซับซ้อนของ Code ไปได้เยอะเลยครับ

ที่ปล่อยบทความช้า เพราะอยากจะให้ทาง JavaScript Bangkok 2.0.0 ปล่อยตัว Video ที่ถ่ายไว้ในงานสู่สาธารณะก่อนครับ