Dokumen ini menjelaskan Praktik Terbaik (Best Practices) untuk mengintegrasikan localcached ke dalam berbagai Framework aplikasi populer.
Prinsip Utama:
- Singleton: Client
localcachedharus di-instansiasi sebagai Singleton. Jangan membuat koneksi baru di setiap request! Koneksi UDS sangat cepat, tapi handshake berulang-ulang tetap memboroskan resource. - Connection Lifecycle: Buka koneksi saat aplikasi start (
Startup), dan tutup saat aplikasi mati (Shutdown) jika memungkinkan.
Di ekosistem .NET, kita menggunakan IServiceCollection untuk Dependency Injection (DI).
Buka Program.cs. Kita akan mendaftarkan LocalCachedClient sebagai service Singleton.
using YourNamespace.Services; // Namespace tempat LocalCachedClient berada
var builder = WebApplication.CreateBuilder(args);
// --- REGISTER SERVICE ---
// Singleton: Dibuat 1x saat aplikasi start, dipakai bareng-bareng.
builder.Services.AddSingleton<LocalCachedClient>(sp =>
{
// Ambil path dari Configuration (appsettings.json) atau default
var path = builder.Configuration["LocalCached:SocketPath"] ?? "/run/localcached.sock";
var client = new LocalCachedClient(path);
client.Connect(); // Connect langsung saat startup
return client;
});
// ------------------------
var app = builder.Build();
// Optional: Pastikan disconnect bersih saat app shutdown
var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(() => {
var client = app.Services.GetService<LocalCachedClient>();
client?.Dispose();
});Cukup tambahkan di Constructor (Constructor Injection).
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly LocalCachedClient _cache;
// DI Container otomatis meng-inject instance singleton tadi
public UsersController(LocalCachedClient cache)
{
_cache = cache;
}
[HttpGet("{id}")]
public IActionResult GetUser(int id)
{
// Gunakan cache
var user = _cache.Get<UserProfile>($"user:{id}");
if (user != null) return Ok(user);
// ... ambil dari DB ...
return NotFound();
}
}Pola yang sama berlaku jika Anda menggunakan Generic Host (Host.CreateBuilder).
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<LocalCachedClient>(sp => {
var client = new LocalCachedClient("/run/localcached.sock");
client.Connect();
return client;
});
services.AddHostedService<MyBackgroundWorker>();
})
.Build();
await host.RunAsync();Jika aplikasi Anda sederhana (tanpa DI Container), gunakan pola static readonly.
public static class AppCache
{
// Lazy initialization thread-safe
private static readonly Lazy<LocalCachedClient> _client = new Lazy<LocalCachedClient>(() =>
{
var c = new LocalCachedClient("/run/localcached.sock");
c.Connect();
return c;
});
public static LocalCachedClient Instance => _client.Value;
}
// Usage:
// AppCache.Instance.Set("key", "val");Node.js bersifat single-threaded event loop, jadi pattern singleton sebenarnya cukup mudah (module caching). Tapi untuk framework terstruktur, ikuti cara ini:
NestJS sangat bergantung pada Module & Provider.
src/cache/cache.module.ts
import { Module, Global } from '@nestjs/common';
import { LocalCachedClient } from './LocalCachedClient'; // path wrapper anda
@Global() // Opsional: Biar bisa dipakai di module lain tanpa import ulang
@Module({
providers: [
{
provide: 'LOCAL_CACHE', // Token Injection
useFactory: async () => {
const client = new LocalCachedClient('/run/localcached.sock');
await client.connect();
return client;
},
},
],
exports: ['LOCAL_CACHE'],
})
export class CacheModule {}src/users/users.service.ts
import { Injectable, Inject } from '@nestjs/common';
// import LocalCachedClient type interface if needed
@Injectable()
export class UsersService {
constructor(@Inject('LOCAL_CACHE') private readonly cache: any) {}
async findOne(id: string) {
const cached = await this.cache.get(`user:${id}`);
if (cached) return cached;
// ... db ...
}
}Untuk framework minimalis, gunakan pola Singleton Module.
// cache.js
const { LocalCachedClient } = require('./LocalCachedClientWrapper');
const client = new LocalCachedClient('/run/localcached.sock');
// Promise koneksi -> Pastikan ditunggu sebelum server listen
const connect = async () => {
await client.connect();
console.log("Cache Connected");
};
module.exports = { client, connect };const express = require('express');
const { client, connect } = require('./cache');
const app = express();
app.get('/user/:id', async (req, res) => {
const data = await client.get(`user:${req.params.id}`);
res.json(data || { msg: 'Not Found' });
});
// Start server setelah connect cache
connect().then(() => {
app.listen(3000);
});PHP request lifecycle biasanya "Born & Die" per request (terutama di FPM). Namun localcached tetap butuh koneksi. Di PHP, kita biasanya register di Service Container.
php artisan make:provider LocalCacheServiceProvider
Buka app/Providers/LocalCacheServiceProvider.php.
public function register()
{
$this->app->singleton(LocalCachedClient::class, function ($app) {
$socketPath = env('LOCALCACHED_SOCKET', '/run/localcached.sock');
$client = new \App\Services\LocalCachedClient($socketPath);
$client->connect(); // Penting: Connect di sini
return $client;
});
}Laravel otomatis meng-inject via konstruktor.
use App\Services\LocalCachedClient;
class UserController extends Controller
{
protected $cache;
public function __construct(LocalCachedClient $cache)
{
$this->cache = $cache;
}
public function show($id)
{
$val = $this->cache->get("user:$id");
return response()->json($val);
}
}Buka/Buat app/Config/Services.php.
namespace Config;
use CodeIgniter\Config\BaseService;
use App\Libraries\LocalCachedClient;
class Services extends BaseService
{
public static function localcache($getShared = true)
{
if ($getShared) {
return static::getSharedInstance('localcache');
}
$client = new LocalCachedClient('/run/localcached.sock');
$client->connect();
return $client;
}
}Usage:
$cache = \Config\Services::localcache();
$data = $cache->get('key');Django tidak punya DI Container bawaan yang "strict", tapi kita bisa init di AppConfig.
Misal di app utama core.
# core/apps.py
from django.apps import AppConfig
from .localcached import LocalCachedClient
# Global var
cache_client = None
class CoreConfig(AppConfig):
name = 'core'
def ready(self):
global cache_client
# Mencegah double reload di dev mode
import os
if os.environ.get('RUN_MAIN', None) != 'true':
return # Skip watcher process
print("Connecting to Cache...")
cache_client = LocalCachedClient("/run/localcached.sock")
cache_client.connect()# core/views.py
from .apps import cache_client
def get_user(request, id):
if cache_client:
user = cache_client.get(f"user:{id}")
# ...FastAPI punya sistem Depends yang powerful.
# dependencies.py
from .localcached import LocalCachedClient
_client = None
async def get_cache_client():
global _client
if _client is None:
_client = LocalCachedClient()
_client.connect()
return _clientfrom fastapi import FastAPI, Depends
from .dependencies import get_cache_client
app = FastAPI()
@app.get("/items/{id}")
async def read_item(id: str, cache = Depends(get_cache_client)):
val = cache.get(f"item:{id}")
return {"id": id, "cached": val}Di Go, best practice-nya adalah tidak menggunakan global variable, melainkan passing dependency via Struct (Receiver).
type Server struct {
Cache *LocalCachedClient
DB *sql.DB
}
func (s *Server) GetUser(c *gin.Context) {
id := c.Param("id")
val, _ := s.Cache.Get("user:" + id)
c.JSON(200, val)
}func main() {
// 1. Init Cache
cache := NewLocalCachedClient("/run/localcached.sock")
err := cache.Connect()
if err != nil { panic(err) }
defer cache.Close()
// 2. Inject ke Server struct
srv := &Server{
Cache: cache,
}
// 3. Setup Router
r := gin.Default()
r.GET("/user/:id", srv.GetUser)
r.Run()
}Spring Boot menggunakan @Bean untuk Dependency Injection.
@Configuration
public class CacheConfig {
@Bean
public LocalCachedClient localCachedClient() {
try {
LocalCachedClient client = new LocalCachedClient("/run/localcached.sock");
client.connect(); // Connect startup
return client;
} catch (Exception e) {
throw new RuntimeException("Failed connect cache", e);
}
}
}@Service
public class UserService {
private final LocalCachedClient cache;
@Autowired // Constructor Injection
public UserService(LocalCachedClient cache) {
this.cache = cache;
}
public User getUser(String id) {
// ... use cache.get(...)
}
}Apapun bahasanya, kuncinya adalah: Buat instance client sekali saja (Singleton), lakukan koneksi di awal, dan inject instance tersebut ke bagian kode yang membutuhkan. Hindari new LocalCachedClient() di dalam fungsi/method yang dipanggil berulang kali.