محسن نوشته

ساخت پروژه Erlang با SEPC
منتشر شده در: — Aug 24, 2019

مثل هر پروژه دیگه ای با سایر زبانهای برنامه نویسی، پروژه های Erlang هم ساختار مختص خودشونو دارن. ابزاری مثل Rebar3 کمک میکنه تا ما براحتی یک پروژه Erlang را ایجاد و نگهداری کنیم، علاوه بر این کمک می کنه تا برای لایه های مختلف (Development، Test، Stage و Production) نسخه تهیه کنیم. توی این پست می خوام ابزاری را با هم بررسی کنیم که مبتنی بر تجربه من ساخته شده و کمک میکنه با سرعت بیشتری پروژه های Erlang را ایجاد و خروجی های مطلوب را آماده کنیم. البته این ابزار هم از سایر ابزارهای استاندارد و معروف مثل Make و Rebar3 کمک می گیره و چیزی که مهمه اینه که فقط کارهای تکراری را برامون با سرعت بیشتری انجام میده.

اسم این ابزار SEPC نام داره و در واقع از حروف اول: Simple Erlang Project Creator گرفته شده. برای استفاده از این ابزار لازمه Erlang نسخه +18 و Rebar3 روی سیستم شما نصب شده باشه. اگه به سایتشون سر بزنید می تونید براحتی نصبشون کنید. برای دریافت SEPC هم نیاز به git دارید. اگه ابزارهایی که گفتم را آماده کرده باشیم، میتونیم دستورات زیر را روی کنسول اجرا کنید:

1
2
3
4
cd /tmp
git clone https://github.com/mohsenmoqadam/SEPC
cd SEPC
./sepc.sh my_app 1.0.0

دستور آخر با استفاده از اسکریپت sepc.sh یک پروژه بنام my_app با ورژن 1.0.0 برامون ایجاد میکنه. حالا وقتشه این پروژه را ران کنیم، برای اینکار دستورات زیر را روی کنسول اجرا میکنیم:

1
2
cd my_app
make rel-dev && make console-dev

بهمین سادگی REPL میاد بالا و پروژه ای که ساختید آماده میشه. شکل زیر REPL را نشون میده. اینجا میتونیم همه دستورات زبان Erlang را بنویسیم و اگه خواستیم ازش خارج بشیم کافیه Ctrl + c را دوبار پشت سر هم فشار بدیم.

sepc-init

حالا بریم ببینیم دیگه SEPC برامون چیکار میتونه انجام بده. یکی از کارهای جالبش اینه که میتونه برای لایه های مختلف Tarball ایجاد کنه. SEPC میتونه برای Production و Stage خروجی Tarball متناسب با پروفایلی که توی rebar.conf تنظیم کردیم، تولید کنه. پروفایل های مختلف را SEPC با تنظیمات پیشفرض توی rebar.conf قرار میده. تنها کاری که لازمه اینه که ما مطابق نیازمون بروزش کنیم و برای گرفتن Tarball یکی از دستورات زیر را استفاده کنیم:

1
2
3
make rel-dev
make rel-stage
make rel-prod

دستور اول release را برای سیستمی که روش دستور اجرا شده آماده میکنه. از اونجایی که Erlang روی این سیستم نصب هستش این نسخه براحتی میتونه استفاده شه. اما دو تا دستور بعدی Tarball را برای لایه های Stage و Production آماده میکنه. نکته قابل توجه اینه که نیاز نیست روی ماشین های Stage و Production از قبل Erlang نصب باشه. همراه Tarballی که آماده میشه همه ملزومات اجرا برنامه روی این ماشین ها قرار میگیره. SEPC پیکربندی Application و EVM برای هر پروفایل را توی دارکتوری config قرار میده. بعنوان مثال پیکربندی مربوط به پروفایل Production را میتونیم توی فایل های prod.sys.config و prod.vm.args ببینیم و مطابق نیازمون بروز کنیم.

خوب حالا وقتشه که دو تا ویژگی مهم SEPC را با هم بررسی کنیم. اگه شما هم مثل من زیاد با Protobuff سروکار داشتید SEPC بشما کمک میکنه براحتی فایل های proto را به ماژول های Erlang تبدیل کنید. SEPC توی دایرکتوری proto دو تا مثال از فایل های proto برامون قرار میده که میتونیم با دستور زیر این فایل ها را به ماژول های Erlang تبدیل کنیم:

1
2
 
make proto

حالا اگه به دایرکتوری های src و include سر بزنیم، میتونیم Moduleها و HRLهای تولید شده را مشاهده کنیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
mohsen@MyMint:/tmp/SEPC/my_app$ ls src/
my_app_app.erl  
my_app.app.src  
my_app_sample_func.erl  
my_app_sample_type.erl  
my_app_sup.erl

mohsen@MyMint:/tmp/SEPC/my_app$ ls include/
gpb.hrl  
my_app.hrl  
my_app_sample_func.hrl  
my_app_sample_type.hrl

اگه بخواهیم داده هایی که روی Network‍ جابجا میکنیم را Serialize کنیم علاوه بر ماژول های بالا نیاز به یک Codec هم داریم. اگه یه سری قواعد را به فایل های proto پروژه اضافه کنیم، SEPC میتونه Codec مناسب را برای این منظور ایجاد کنه. برای این کار لازمه اولین خط هر فایل proto و هر آبجکتی که توی این فایل ها قرار میگیره کامنت ویژه ای داشته باشه. مشابه قطعه کد زیر:

1
2
3
4
5
6
// +> category = REQUEST
package my_app.sample.func;
import "my_app.sample.type.proto";
// *> code = 151
message Call {
}

علائمی که بعد از کامنت میان (<+,<*) درواقع نشانگرهایی هستند که SEPC برای ساختن Codec ازشون استفاده می کنه. با این تفاسیر ما باید برای هر فایل proto مشخص کنیم که آبجکت های آن به چه خانواده ای از API ما تعلق دارند (Category) و هر آبجکت یک فایل با چه شماره ای قراره کد بشه. این کدینگ هم لازمه چون Codecی که SEPC میسازه درواقع برای ما یک Binary-RPC را پیاده سازی میکنه. هر آبجکت قبل از ارسال روی Wire داخل فریم مشخصی قرار می گیره. این فریم از دو بخش Header و Body ساخته میشه. Header حاوی اطلاعات مربوط به نوع و کد ردگیری و… و Body هم حاوی مقدار Binary یک آبجکت هستش. شکل زیر کمک میکنه به درک بهتری از موضوع برسیم:


Kubenetes

ما میتونیم با SEPC چهار نوع فریم را پیاده سازی کنیم. این فریم ها عبارتند از:

نکته ای که باید دقت کنیم اینه که Header هر فریم فیلدی به نام TrackingID داره و اینحوری ما میتونیم بفهمیم که پاسخ دریافت شده مربوط به چه سوالی هست. من سوالات را توی فایل های proto از نوع func و پاسخ ها را توی فایل های Proto از نوع Type دسته بندی می کنم. اگه به نامگذاری فایل های Proto که توسط SEPC ایجاد شده دقت کنید متوجه این موضوع خواهید شد.

1
make codec 
این دستور یک Module جدید بنام my_app_codec ایجاد میکنه:

1
2
3
4
5
6
7
mohsen@MyMint:/tmp/SEPC/my_app$ ls src/
my_app_app.erl  
my_app.app.src  
my_app_codec.erl  
my_app_sample_func.erl  
my_app_sample_type.erl  
my_app_sup.erl

برای اینکه با این ماژول کار کنیم لازمه دستور زیر را اجرا کنیم:

1
make rel-dev && make console-dev 

اگه به فایل my_app.sample.func.proto نگاه کنید میبینید توش یک آبجکت بنام Call با شماره 151 تعریف شده. وقتی Protoها را کامپایل میکنیم (make proto) معادل Erlang این آبجکت برامون ساخته میشه. بعنوان مثال معادل آبجت Call رکورد {}'my_app.sample.func.Call'# هستش. حالا می میخواهیم این رکورد را برای ارسال با فریمینگ بالا روی Wire آماده کنیم. برای اینکار دستور زیر را روی REPL اجرا کنید:

1
2
rr('my_app_sample_func').
my_app_codec:encode(#'my_app.sample.func.Call'{}, 123).

خروجی دستور بالا مشابه زیر هستش:

1
{ok,<<0,0,0,10,0,151,1,0,0,123>>,123}

که یک سه تایی مرتب هستش و عضو دومش Binary هست که میتونیم روی Wire ارسال کنیم. حالا فرض کنید همین Binary را بخواهیم به ترم Erlang تبدیل کنیم. برای اینکار دستور زیر را روی REPL وارد کنید:

1
2
rr('my_app_sample_func').
my_app_codec:decode(<<0,0,0,10,0,151,1,0,0,123>>).

خروجی این دستور مشابه زیر هستش:

1
{<<>>,[{#'my_app.sample.func.Call'{},123,'REQUEST'}]}

این خروجی یک ذوج مرتب هستش که عضو دوم این ذوج ترم معادل Erlang برای Binary مورد نظر هست.

نکته: آرگومان اول تابع decode درواقع یک Buffer هستش و این تابع تلاش میکنه همه فریم هایی که میشه از این بافر استخراج کرد، را استخراج کنه. و اگه بخشی از بافر قابل استخراج نبود، اون بخش را بدون دستکاری شدن بر میگردونه!. برای درک این موضوع دستور زیر را روی REP اجرا کنید:

1
2
rr('my_app_sample_func').
my_app_codec:decode(<<0,0,0,10,0,151,1,0,0,123,0,0,0,12>>).

خروجی دستور مشابه زیر هستش:

1
{<<0,0,0,12>>,[{#'my_app.sample.func.Call'{},123,'REQUEST'}]}

اگه به عضو اول ذوج مرتب خروجی نگاه کنیم متوجه خواهیم شد که تابع decode نتونسته باینری <<0,0,0,12>> را کد گشایی کنه!

پیروز باشید.