Redis cache decorator for Hexagonal architecture in NestJS
31 Jan 2025 05:30
Written by: Yosapol Jitrak
ล่าสุด ผมได้มีโอกาสไปพูดในงาน JavaScript Bangkok 2.0.0 ในหัวข้อ Redis cache decorator for Hexagonal architecture in NestJS แต่ในงานมีเวลาจำกัด และจอมีปัญหา เลยคิดว่าจะมาเขียนเล่าใน Blog อีกครั้ง เพื่อให้คนที่สนใจสามารถอ่านเพิ่มเติมได้
เราจะมาเล่าถึงปัญหากันก่อน ปัจจุบันงานที่ทำผมทำอยู๋ใช้ MongoDB Atlas และทาง Business อยากจะลด Cost ครับ ซึ่งราคาจะเป็นตามรูปด้านล่างนี้ครับ
แน่นอนปัญหานี้เราน่าจะใช้ Cache มาช่วย เพื่อลด Hit ของ Database ครับ ปัจจุบันก็คงจะเลือกใช้เป็น Redis ครับ
Project ที่ผมทำอยู่เป็น NestJS และเป็น Hexagonal Architecture ครับ ซึ่งการทำ Cache สามารถใช้ Cache Manager ของ NestJS ได้เลย แต่ผมก็ติดปัญหาอยู่ตรงเมื่อเราต้องการทำ Invalidate cache ทันทีเมื่อมีการ Update ข้อมูลใหม่ ๆ ใน Database ครับ ซึ่ง Cache Manager ของ NestJS ก็ยังไม่มีตรงส่วนนี้ เราสามารถทำได้เพียงแค่ใช้ Time To Live (TTL) เท่านั้น ซึ่งหมายความว่ายังไม่ตอบโจทย์การใช้งานของผม
Credit: NestJS Cache
ก่อนจะไปถึงวิธีแก้ปัญหา ผมขอเล่าถึง Decorator ใน TypeScript ก่อน เพราะจะเป็นสิ่งที่ช่วยเราในการแก้ไขปัญหา ใน TypeScript เรามีสิ่งที่เรียกว่า Decorator อยู่ ถ้าเปรียบเทียบให้เห็นภาพ ภาษาอื่น และ Framework อื่น ๆ จะมีสิ่งที่คล้าย ๆ กันอยู่ครับ เรามาลองดูกันว่ามีตัวไหนบ้าง
Credit: Spring Annotations Cheat Sheet
Credit: NUnit Attribute
คราวนี้เราลองมาดูตัวอย่าง Decorator ของ NestJS กันครับ
ในบทความนี้คงไม่ได้เล่าลงลึกมากครับ หลัก ๆ แล้วตัว Hexagonal Architecture นั้นจะแบ่ง Layer เป็นชั้น ๆ โดยจะทำให้ส่วนที่ผูกติดกับ Framework รวมถึงภายนอกอย่าง Database หรือ API นั้นแยกออกจากส่วนที่เป็น Business Logic ครับ ซึ่งจะเพิ่มความยืดหยุ่นกับ Code ของเราสามารถเปลี่ยนแปลงไปใช้ Framework และ Database อื่นได้ง่ายขึ้น รวมถึงสามารถทำ Unit Test ของ Business Logic ได้ง่ายขึ้นมากครับ โดยภายนอกและภายใน Application จะคุยกันผ่าน Port ครับ โดย Port นั้นจะเป็นเพียงแค่ Interface หลังจากนั้นเราค่อยเอาตัวที่จะเชื่อมต่อมา Implement จริงเป็น Concrete class อีกทีนึงครับ
Credit: Hexagonal Architecture, there are always two sides to every story
Credit: DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together
ยกตัวอย่างตามรูปด้านล่างนี้ครับ
หลังจากปูพื้นกันมาแล้ว เรากลับมาเข้าเรื่องปัญหาของเราดีกว่าครับ เราจะแก้ปัญหากันอย่างไรได้บ้าง
คราวนี้เราลองมาใช้ความคิดต่ออีกหน่อย เห้ยจริง ๆ เราใช้ Decorator ของ NestJS เต็มบ้านเต็มเมืองใน Code ของเราอยู่แล้วนี่นา คิดออกมาได้เป็นไอเดียสุดท้ายครับ
กลับมาดูกันว่า Decorator ของ Method เราทำยังไงกับมันได้ครับ
จะเห็นว่าเราสามารทำอะไรก่อน และหลัง Method ของเราได้ครับ
ซึ่งมีคนคิดคล้ายเรา ๆ เลย ลองดูตัวอย่างด้านล่างนี้ครับ
จะเห็นว่าเราเก็บ Original method ไว้ และสุดท้ายคืน descriptor ตัวเดิมกลับออกไป
คราวนี้ลองดู Implement จริงครับ Credit: How To Create a Custom Typescript Decorator
คราวนี้ลองมาดูไอเดียของเรากันครับ
หลายคนมาถึงตรงนี้แล้วอาจจะงงว่า Combination key คืออะไร ลองดูตัวอย่างด้านล่างนี้ครับ
สรุปก็คือความเป็นไปได้ของ Cache key ของเราทั้งหมดกับ Repository นั้นครับ
จะเห็นว่า Code เราเวลาจะต้องการจะทำ Cache ก็แค่แปะ CacheForRepository decorator เข้าไปใน Method ที่เราต้องการจะทำ Cache ได้เลยครับ โดยตัวอย่างนี้ผมให้ Focus แค่ส่วน Cache ก่อนนะครับ โดยอันนี้ Key ที่จะทำการ Cache คือ items:all นั้นเอง
คราวนี้ลองมาดูตัวอย่าง Cache อื่น ๆ กันบ้างครับ
ตัวอย่างนี้จะเป็น Key items:{id:?}
เราสามารถแทนค่า ? ได้จาก itemId ที่ส่งเข้ามาใน Method นี้ครับ
ลองไปดูตัวอย่างถัดไปกันครับ
items:{status:?,color:?}
เราสามารถแทนค่า ? ได้จาก status และ color ที่ส่งเข้ามาใน Method findByStatusAndColor ครับitems:{country:?,category:?}
เราสามารถแทนค่า ? ได้จาก country และ category ที่ส่งเข้ามาใน Method findByCountryAndCategory ครับต่อมาเรามาดูส่วนของ Invalidate Cache กันครับ
[['id'], ['country', 'category'], ['status', 'color']]
ซึ่งจะทำการ Invalidate ทุก Key ที่มีความเป็นไปได้ทั้งหมดที่อยู่ใน Array นี้ครับitems:all
, items:{id:507f1f77bcf86cd799439011}
, items:{country:Thailand,category:Clothes}
และ items:{status:Available,color:Red}
ตามข้อมูลที่ Update เข้ามาครับคราวนี้ลองมาดูตัว Decorator ที่เราเขียนกันครับ
ตัว Cache Decorator จะมีหน้าตาตาม Code ด้านล่างนี้ครับ
ตัว Invalidate Decorator จะมีหน้าตาตาม Code ด้านล่างนี้ครับ
สำหรับตัวอย่างนี้ผมไม่ได้เล่าทั้งหมดครับ แต่จะเป็น 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 ที่ถ่ายไว้ในงานสู่สาธารณะก่อนครับ