همانطور که در مطلب آشنایی با
معماری Microservices گفته شد، Seneca یک فریمورک مبتنی بر Node.js برای ساخت برنامههای سمت سرور بر مبنای معماری Microservices با هسته Monolithic است. در این مطلب قصد ارائه یک مثال عملی را بر اساس این فریمورک ندارم. هدف، آشنایی با اجزای اصلی Seneca و چهارچوب کاری آن است.
فواید استفاده از فریمورک Seneca
- فریمورک Seneca کدنویسی برای ایجاد درخواستها، ارسال پاسخ به درخواستهای رسیده و تبدیل دادهها را که از روتینهای هر سرویسی میتوانند باشند، سادهتر میکند.
- فریمورک Seneca با معرفی ایده Actionها و Pluginها که جلوتر توضیح داده خواهند شد، روند تبدیل کامپوننتهای یک برنامه Monolithic را به نوع سرویسی، تسهیل میکند. روندی که به صورت عادی میتواند کاری طاقت فرسا باشد و نیاز به Refactoring زیادی دارد.
- سرویسهای نوشته شده با زبانهای برنامهنویسی مختلف یا فریمورکهای متفاوت میتوانند با سرویسهای نوشته شده توسط فریمورک Seneca در ارتباط و تبادل باشند.
نصب فریمورک Seneca
نصب Seneca تفاوتی با نصب سایر فریمورکها توسط npm ندارد. مثلا میشود فایلی با نام package.json را با تنظیماتی که در پی خواهد آمد داشت و با اجرای دستور npm install در خط فرمان، Seneca را نصب کرد. البته دستور نصب را باید در پوشهای که فایل package.json در آن ایجاد شدهاست، اجرا کرد.
{
"name": "seneca-example",
"dependencies": {
"seneca": "0.6.5",
"express": "latest"
}
} بعد برای استفاده از فریمورک نصب شده باید نمونهای از آن را ایجاد کنیم.
var seneca = require("seneca")(); Actionها
Actionها عملکرد بخصوصی را ارائه میدهند که باید به نمونهای از فریمورک Seneca که ایجاد کردهایم، اضافه شوند. اینکه یک Action چه عملکرد بخصوصی را ارائه میهد، در زمان اضافه کردن آن به نمونهای ایجاد شده، مشخص میشود. در
مطلب گذشته گفته شد که برای تغییر یک برنامه Monolithic به نوع سرویسی، میشود فقط کامپوننتهای دردسر ساز را تغییر داد. Actionها عملکردهای آن کامپوننتهای مشکل ساز را به عهده خواهند گرفت. زمان اضافه کردن یک Action به نمونهای از Seneca، از متد add استفاده میکنیم که دو آرگومان را دریافت میکند. اولین آرگومان، یک رشته JSON یا یک object است که هویت عملکرد Action را نشان میدهد و دومی در واقع یک متد Callback است و زمانیکه Action فراخوانی میشود، اجرا خواهد شد.
seneca.add({role: "accountManagement", cmd: "login"}, function(args, respond){
});
seneca.add({role: "accountManagement", cmd: "register"}, function(args, respond){
}); در مثال بالا دو Action را به نمونهای از Seneca اضافه کردهایم. خواص role و cmd موارد بخصوصی نیستند. میشود آنها را با موارد دلخواه، بسته به عملکردی که از Action انتظار داریم، جایگزین کنیم. برای فراخوانی Actionهایی که اضافه کردهایم، باید از متد act استفاده کنیم. البته متد act میتواند Actionهایی را که در سایر سرویسها قرار دارند هم فراخوانی کند. اولین آرگومان act، برای تطبیق با الگوهایی که در زمان اضافه کردن Actionها به نمونه تنظیم شدهاند، استفاده میشود. دومین آرگومان آن هم باز یک Callback است و در زمانیکه Action فراخوانی شدهاست اجرا میشود.
seneca.act({role: "accountManagement", cmd: "register", username:
"parham", password: "12345!"}, function(error, response){
});
seneca.act({role: "accountManagement", cmd: "login", username:
"parham", password: "12345!"}, function(error, response){
}); در مثال بالا و در پارامتر اول، مشخصههای بیشتری نسبت به الگوی Actionهایی که اضافه کردهایم، دیده میشود. این مسئله مشکلی ایجاد نمیکند و به هر حال Seneca تمامی الگوهای مشابه را شناسایی میکند و آن موردی که بیشترین تطبیق را دارد، فرا میخواند.
Pluginها
Pluginها در Seneca در واقع بسته بندیهایی از Actionهایی هستند که کاربرد مشابهی دارند. در مثال بالا دو Action داشتیم که یکی عملکرد ورود را برعهده دارد و دیگری ثبتنام. از آنجایی که هر دو برای بخش مدیریت کاربران تشابهاتی دارند، میشود آنها را در یک Plugin بسته بندی کرد. ارزش Pluginها زمانی است که میخواهیم آنها را توزیع کنیم. با توزیع Plugin، تمام Actionهای زیرمجموعه آن همزمان توزیع میشوند. Pluginها را میشود در قالب توابع یا ماژولها ایجاد کرد.
function account(options){
this.add({init: "account"}, function(pluginInfo, respond){
console.log(options.message);
respond();
})
this.add({role: "accountManagement", cmd: "login"}, function(args, respond){
});
this.add({role: "accountManagement", cmd: "register"}, function(args, respond){
});
}
seneca.use(account, {message: "Plugin Added"}); در مثال بالا از روش تابع برای ایجاد Plugin استفاده شدهاست. Plugin را ایجاد میکنیم و Actionهایی را که میخواهیم به Plugin اضافه شوند، توسط this اضافه میکنیم و در نهایت با متد use به نمونه Seneca میگوییم که از Plugin ساخته شده، استفاده کند. کلمه کلیدی This درون Plugin به نمونهای از Seneca که ایجاد کردهایم، ارجاع دارد. یعنی هر this.add برابر با seneca.add میباشد. نکته باقی مانده، Action اضافهای است با مشخصه {init: "account"} که چه نقشی دارد. این Action نقش یک سازنده را برای مقداردهی اولیه، دارد. برای نمونه میشود از آن برای ارتباط با پایگاه داده، پیش از اجرا شدن سایر Actionها که نیاز به آن ارتباط دارند، استفاده کرد. مثال بالا را میشود با یک ماژول هم پیادهسازی کرد.
module.exports = function(options){
this.add({init: "account"}, function(pluginInfo, respond){
console.log(options.message);
respond();
})
this.add({role: "accountManagement", cmd: "login"}, function(args, respond){
});
this.add({role: "accountManagement", cmd: "register"},function(args, respond){
});
return "account";
}
seneca.use("./account.js", {message: "Plugin Added"}); ماژول، نام Plugin را برگشت میدهد که با فرض بر اینکه ماژول در فایل account.js قرار دارد، با متد use به نمونه Seneca میگوییم که از Plugin ساخته شده استفاده کند.
Serviceها
یک سرویس، یک نمونه از فریمورک Seneca است که Actionهایی را برای استفاده بیرونی، در معرض شبکه قرار میدهد.
var seneca = require("seneca")();
seneca.use("./account.js", {message: "Plugin Added"});
seneca.listen({port: "9090", pin: {role: "accountManagement"}}); در مثال بالا، نمونهای از فریمورک Seneca ایجاد شده، مجموعهای از Actionها تحت یک Plugin به آن اضافه شده و در نهایت Actionهایی که در زمان ساخت و اضافه کردن آنها نقش accountManagement گرفتهاند، برای استفاده در معرض شبکه و در بستر HTTP قرار میگیرند. هر دو بخش port و pin، اختیاری هستند و با صرفنظر از این دو بخش، اولا نمونهی Seneca برای دریافت درخواستهای ورودی، به درگاه پیش فرض 10101 گوش میدهد و ثانیا تمامی Actionهایی را که به نمونه اضافه کردهایم، در معرض شبکه قرار میگیرند.
در سمت دیگر، برای اینکه سایر سرویسها و برنامههای Monolithic قادر باشند Actionهای در معرض شکبه قرار داده شده را فراخوانی کنند، باید آنچه را که در معرض قرار داده شده، در خود ثبت کنند. برای مثال سرویس دیگری از Seneca میتواند از قطعه کد زیر استفاده کند.
seneca.client({port: "9090", pin: {role: "accountManagement"}}); در اینجا هم موارد port و pin اختیاری هستند. اگر سرویسی که میخواهیم ثبت کنیم در سرور دیگری باشد، بایستی خاصیت host و با مقدار آدرس IP سرور مورد نظر در زمان ثبت، اعمال شود. سوالی که باقی میماند این است که یک سرویس چطور Actionی از یک سرویس دیگر را فراخوانی میکند؟ در بخش Actionها آمد که برای فراخوانی یک Action از متد act، از نمونهی Seneca استفاده میکنیم. رفتار Seneca به این صورت است که ابتدا بر اساس امضای Action درخواست شده، Actionهای محلی را که به سرویس جاری اضافه شدهاند، جستجو میکند. اگر تطبیقی نیافت به سراغ Actionهای ثبت شده خارجی که دارای خاصیت pin هستند، خواهد رفت و در نهایت اگر آنجا هم موردی نیافت، برای تک تک سرویسهایی که آنها را ثبت کرده، اما خاصیت pin را ندارند، درخواستی را ارسال میکند.
برای اطلاعات بیشتر به بخش مستندات فریمورک Seneca رجوع کنید.