استفاده از GenServer برای ذخیره یک مقدار

elixir
phoenix
genserver

#1

سلام
در یک پروژه ای که با elixir و framework phoenix نوشت شده ، نیاز هست تا داده های زیادی به صورت موقت در جایی ذخیره بشن . چون داده ها زیاد بودن به صورت ترکیبی از genserver برای ذخیره در state و session استفاده کردم

پارامترها به صورت map هست

%{
    "_csrf_token" => "DhA3FRwIGxQNQiYGfC4GJgAAKSOdf2fsdPDFCqZ5lOAydRPnEmmA==",
    "_utf8" => "✓",
    "ersal" => "hozori",
    "name_family" => "مجتبی ناصری",
    "number" => "9922365", 
    "phone_number" => "0911333333",
    "price_of_card" => "1320",
    "price_of_text" => "200",
    "receive" => "person",
    "writing-kind" => "1"
  }

و من یک uuid هم دارم که اونا رو به صورت map ذخیره کردم . یعنی مقداری که در state ذخیره میشه به این صورت هست . برای دو کاربر

%{
  "793edde3-bcc0-4719-9738-c527f168163e": %{
    "_csrf_token" => "DhA3FRwIGxQNQiYGfC4GJgAAKSOdf2fsdPDFCqZ5lOAydRPnEmmA==",
    "_utf8" => "✓",
    "ersal" => "hozori",
    "name_family" => "مجتبی ناصری",
    "number" => "9922365", 
    "phone_number" => "0911333333",
    "price_of_card" => "1320",
    "price_of_text" => "200",
    "receive" => "person",
    "writing-kind" => "1"
  },
  "963018d7-6e03-4f8d-a6f4-31298fc89128": %{
    "_csrf_token" => "DhA3FRwIGxQNQiYGfC4GJgAAKSOdf2fsdPDFCqZ5lOAydRPnEmmA==",
    "_utf8" => "✓",
    "ersal" => "hozori",
    "name_family" => "مجتبی نصیری",
    "number" => "46513", 
    "phone_number" => "0921433333",
    "price_of_card" => "13۶0",
    "price_of_text" => "200",
    "receive" => "person",
    "writing-kind" => "1"
  }
}

در سمت کلاینت همون phoenix بعد از اینکه کاربر فیلدهای مورد نظرش رو انتخاب کرد چک میشه اگر در session برای اون کاربر uuid وجود نداشت یک uuid براش درست میکنم و در session مرورگر ذخیره میکنم بعد اون uuid رو با پارامترهایی که انتخاب کرده بود به genserver میفرستم تا ذخیره بشن . اگر uuid برای اون کاربر وجود نداشت که همون لحظه یکی درست میکنم براش .

از تابع زیر چند تا وجود داره تا در حالت های مختلف عمل بکنه . در مثال زیر فقط ذخیره کردن مقادیر در genserver و session رو نوشتم . در توابع دیگه مقادیر مورد نظر رو با توجه به uuid که در session ذخیره شده از genserver میخونم

def sending_info(conn, %{"receive" => "person"} = params) do
    #conn = put_session(conn, :invoice, params)
  
    with {:ok, uuid} <- check_session(conn, :uuid_genserver) do
        # فراخوانی تابع ذخیره کننده جن سرور
        InvoiceGenserver.save_params(uuid, params)

        redirect(conn, to: "/invoice")
    else 
      _ ->
        uuid = Ecto.UUID.generate
        # فراخوانی تابع ذخیره کننده جن سرور
        InvoiceGenserver.save_params(uuid, params)
        
        put_session(conn, :uuid_genserver, uuid)
        |> redirect(to: "/invoice")
    end 
  end

برای ذخیره کردن این تابع زیر رو نوشتم که پارامترها و uuid رو بهش میدم

save_params(uuid,params)

هر بار که تابع بالا فراخوانی میشه map موجود در state رو در صورت وجود uuid آپدیت میکنه یا در صورت نبودن ان uuid در map مقادیر رو درج میکنه چون از Map.update استفاده کردم

چند تابع هم برای گرفتن مقدار از state یا حذف از اون نوشتم

همه توابع به صورت زیر هست البته خیلی ساده نوشتم:

فراخوانی در فایل application.ex

# شروع جن سرور  با مقدار اولیه که یک مپ هست
def start(_type, _args) do
    children = [
       {WeddingCard.Db.Invoice.InvoiceGenserver, %{}} 
    ]
end

توابع genserver

defmodule WeddingCard.Db.Invoice.InvoiceGenserver do

use GenServer

    @moduledoc """
    Documentation for Invoice.
    """
  
    # client api
    def start_link(init_param) do
      GenServer.start_link(__MODULE__, init_param, name: __MODULE__, debug: [:trace])
    end
    
    # مقدار دهی اولیه به جن سرور که یک لیست دادیم
    def init(init_param) do
        {:ok, init_param}
    end

    # خواندن همه مقادیر از  جن سرور
    def load_all_params do 
        GenServer.call(__MODULE__, :load_all)
    end


    #خواندن یک مقدار خاص با استفاده از 
    # uuid
    def load_one_params(uuid) do 
        GenServer.call(__MODULE__, {:load_one, uuid})
    end

    @doc """
       تابع ذخیره کردن پارامترها در استیت
        ورودی پارامترها و 
        uuid

        اگر 
        uuid
        جدید باشه با پارامترها ذخیره میکنه
        وگرنه پارامترها رو آپدیت میکنه
    """
    def save_params(uuid,params) do
        GenServer.cast(__MODULE__, {:save, uuid ,params})
    end


    @doc """
       حذف یک پارامتر با استفاده از 
       uuid
    """
    def delete_one_params(uuid) do
        GenServer.cast(__MODULE__, {:delete_one, uuid})
    end

    @doc """
        حذف همه مقادیر موجود در جن سرور
    """
    def delete_all_params do
        GenServer.cast(__MODULE__, :delete_all)
    end

    
    #===============================================

    #server api
    # هندلر برای خوندن همه پارامترها
    def handle_call(:load_all, _from, init_param) do 
        {:reply, init_param, init_param}
    end


    # هندلر برای خوندن یک پارامتر
    def handle_call({:load_one, uuid}, _from, init_param) do 
        new_state = Map.get(init_param, String.to_atom(uuid))
        {:reply, new_state, init_param}
    end

    # هندلر برای ذخیره پارامترها
    def handle_cast({:save, uuid, params}, init_param) do
     new_state = Map.update( 
         init_param , 
         String.to_atom(uuid) , 
         params, fn _old_map -> params end 
    )

    IO.inspect new_state;
         {:noreply, new_state }
    end


    # هندلر برای حذف یک پارامتر مشخص با استفاده از 
    # uuid
    def handle_cast({:delete_one, uuid}, init_param) do
        new_state = Map.delete(init_param, String.to_atom(uuid))
        {:noreply, new_state }
    end

    # هندلر برای حذف پارامترها
    def handle_cast(:delete_all, _init_param) do
        {:noreply, %{}}
    end

end

چون با بستن مرورگر session کاربر از بین میره ، میخوام در زمان درست کردن مقدار uuid تایم جاری رو هم در map ذخیره کنم بعد با یک تابع در genserver که هر چند ساعت این تایم رو برای هر uuid چک کنم اگر بیشتر از 10 دقیقه بود اون رو پاک بکنه . البته هنوز پیاده نکردم چون نمیدونم این روش درست هست یا نه ؟

سوالی که برام پیش اومده اینه که آیا نحوه استفاده از genserver به این صورت درسته ؟
اگر درست هست آیا برای نیازی که در بالا تعریف شد اینطور استفاده کردن از genserver مشکلی ایجاد نمیکنه ؟
کلا در مورد موارد بالا هر مشکلی وجود داره بیان کنید

با تشکر از همه دوستان


#2

بله این طور کار برای genserver بسیار معقول هستش

نه ولی قبلش خوب این عملکرد تست کنید


#3

از طریق کنسول توابع رو تست کردم . تست های دیگه رو هم انجام میدم

در مورد بالا نظری دارین؟


#4

درود خدمت سام عزیز آیا این روش ایزوله هست ؟


#5

به تابع send_after نگاه کنید میتونید با زمان بندی یک کاری انجام بدید

https://hexdocs.pm/elixir/GenServer.html


#6

درود شهریار عزیز
منظور از ایزوله چیه
روش دیگه این کار استفاده از ردیس هستش


#7

راه بهتری هم برای انجامش هست یا فقط همین مورد باید استفاده بشه ؟


#8

با ردیس اینکار بسیار ساده تره اگه امکان داره ردیس داشته باشی
چون با ردیس میتونی زمانه expire شدن کلید را مشخص کنی و خودش اتوماتیک کلید را پاک میکنه
https://redis.io/commands/setex


#9

امکان استفاده از ردیس هست ولی میخواستیم که بدون اون انجامش بدیم . با جن سرور نمیشه جوری دیگه نوشت ؟


#10

با genserver هم همینطوری که صحبتش هست میشه


#11

درود خدمت سام عزیز. منظور این بود دیدم کلا به صورت مپ هست فکر کردم اون شعاری داره که هر درخواست در یک پروسز به اون صورت نباشه که فرمودید متداوله و مشکلی نداره.

راستی در انجمن الکسیر به این کتاب خانه آشنا شدم


که دانلود خوبی هم داخل hex.pm داشت به نظرتون بهتر نیست کلا بجای redis از این استفاده بشه ؟ کتابخانه های redis روی الکسیر یکمی جالب نیستند خیلی دیر به دیر آبدیت می شند و فقط یک اتصال ساده روش نوشته شده و داکیومنت درست حسابی هم ندارند


#12

اگر تمامه نیاز های برنامه شما رو cache ساده برطرف میکنه cachex گزینه خوبیه

قسمت key expiration