احتمالا اگر با فرمورکهایی مثل جنگو، ریلز یا ASP کار کرده باشید، عبارت MVC رو به کرات مشاهده کردید. در این پست، قصد دارم تا در مورد این معماری توضیحاتی ارائه کنم. در هر بخش هم، یک مثالی از فرمورک روبی آن ریلز آوردم.
قبل از شروع بعنوان سلب ادعا عرض کنم که شما نیاز دارید که خودتون، روبی آن ریلز رو جداگانه یاد بگیرید و این مطلب، مطلقا آموزش روبی آن ریلز نیست.
معماری MVC یعنی چی؟
خب MVC یک مخففه برای Model, View و Controller. در این معماری، یک نرمافزار (یا بهتره بگیم ایده/بیزنس) رو به سه موجودیت مدل، ویو و کنترلر تقسیم میکنیم و هر قسمت ایده یا بیزنس رو به یکی از اینها تبدیل میکنیم. در واقع، فرمورکها اینجان که ما فرایند ساخت این قضایا رو سریعتر پیش ببریم.
پس، بهتره جای این که صرفا دنبال زیربغل مار بگردیم، هر موجودیت رو جدا تعریف کنیم و ببینیم تهش به چی میرسیم!
مدل – Model
مدل، همونطور که از اسمش پیداست؛ یک مفهوم انتزاعی از یک موجودیته. بذارید یک مثال ببینیم. بیاید یک پست وبلاگ رو بررسی کنیم.
پست وبلاگ چیا داره؟ پست وبلاگ در نظر من اینها رو حتما داره:
- عنوان که به عنوان یک رشته متنی در نظر گرفته میشه.
- عنوان کوتاه یا slug که این هم یک رشته متنی در نظر گرفته میشه.
- متن بدنه یا body که «متن» در نظر گرفته میشه.
توجه کنید که text از نظر اکثر فرمورکها با string متفاوته. این تفاوت رو احتمالا در مستندات فرمورکی که باهاش کار میکنید میتونید پیدا کنید.
حالا این مدل رو چطوری میسازیم؟ در ریلز به این صورت میتونیم یک مدل بسازیم:
rails generate model Post title:string slug:string body:text
بعد از اون هم به این شکل مایگرشنها رو اجرا میکنیم:
rails db:migrate
خب الان چی داریم؟!
الان چیزی نداریم جز یک مدل، که صرفا میگه «تعریف من از پست چیه». این تعریف کجا استفاده میشه؟ در دیتابیس. خب پس یعنی چی؟ بیاید یک تعریف کلی از مدل ارائه کنیم:
مدل عبارت است از تعاریف انتزاعی و ویژگیهاشون که در یک پایگاه داده ذخیره میشه.
خب، الان که میدونیم مدل چیه، چطور میتونیم یک نمونه ازش اجرا کنیم؟ برای نمونه در ریلز، ما اول کنسول ریلز رو باز میکنیم:
rails c
و سپس این دستور رو در کنسول وارد میکنیم:
Post.create(:title => "Hello, World", :slug => "hello-world", :body => "This is a first post made the wrongest way possible")
و به این شکل، ما یک پست ساختیم. اما آیا روش بهتری هم هست؟ بله هست!
کنترلرها – Controllers
طبیعتا وقتی به شما یک API داده میشه، از شما نمیخوان که با دستورات روبی پست بسازید، بهروز کنید یا حذف کنید! چیزی که به شما میدن چنین چیزیه:
GET /api/v1/posts
و این باید لیست همه پستها رو برگردونه. پس چطور این اتفاق میافته؟ با موجودیتی به اسم «کنترلر». کنترلر کارش چیه؟ کنترلر همونطور که از اسمش پیداست، میتونه کنترل کنه (یا بعبارتی چطور چشمبسته غیب بگیم؟). کنترلر وظیفهش انجام عملیات CRUD روی مدله.
حالا CRUD چیه؟ این هم یه مفهومی در بطن MVC و Rest API عه که باید بدونید چی به چیه و چی کار میکنه. عبارت CRUD مخفف Create, Restore, Update و Destroy عه. بیاید با هم دقیق بررسی کنیم:
- حرف C یا Create : یعنی کنترلر باید بتونه برای من، یک نمونه از مدلم رو بسازه.
- حرف R یا Restore (که در بعضی منابع Retrieve هم گفتن) : یعنی کنترلر باید بتونه محتوای یک مدل یا همه مدلها رو به من نشان بده.
- حرف U یا Update : یعنی کنترلر باید بتونه در وضعیت یک نمونه از مدل، تغییری ایجاد کنه.
- حرف D یا Destroy : یعنی کنترلر باید بتونه یک نمونه از مدل رو حذف کنه.
شما همون پست بلاگ رو در نظر بگیرید. این پست ممکنه نیاز داشته باشه که ویرایش بشه، نیازمند اینه که گاهی حتی حذف کامل بشه و … . این کار رو با کنترلر انجام میدیم. حالا چطوری یک کنترلر میسازیم؟ به این شکل:
rails generate controller api/v1/posts
سپس درون فایل کنترلر، این دو تابع رو برای حرف R اضافه میکنیم:
def index @posts = Post.all render json: @posts end def show @post = Post.find(params[:id]) render json: @post end
در یک تابع، قراره که کل پستها رو ببینیم و در یکی، فقط اونی که با ID مورد نظر ما درخواست شده نمایش داده میشه. خب، الان کافیه با ابزاری مثل curl تست کنیم API مون رو.
~/playground/mvc-tutorial/ [master] curl http://localhost:3000/api/v1/posts/1 {"id":1,"title":"Hello, World","slug":"hello-world","body":"This is a first post made the wrongest way possible","created_at":"2021-03-23T16:19:41.950Z","updated_at":"2021-03-23T16:19:41.950Z"}
حالا جهت این که C هم ببینیم، اون هم به کنترلر مربوطه اضافه میکنیم، اما بقیهش رو از مستندات ریلز میتونید یاد بگیرید 😁
پس این تابع رو به فایل کنترلرمون اضافه میکنیم:
def create @post = Post.new(post_params) if @post.save render json: {:post => @post, :status => success} else render error: {:error => "Failed"} end end
این تابع چی کار میکنه؟ اول میاد از طریق یک تابع خصوصی به اسم post_params تعدادی از پارامترها مثل عنوان، عنوان کوتاه و متن بدنه رو دریافت میکنه، یک نمونه از مدل پست میسازه. اگر ذخیرهسازی پست با موفقیت انجام شه، محتوای پست رو به ما نمایش میده.
با ابزار curl به این شکل میتونیم از این بخش کنترلر استفاده کنیم:
curl -X POST -H 'Content-Type: application/json' -i http://localhost:3000/api/v1/posts --data '{ "title":"A post from a request", "slug":"a-post-from-a-request", "body":"This post has been create using an API call, to show you how awesome MVC can get!" }'
البته حواستون باشه در این مورد، حتما باید هدر Content-Type رو ارسال کنیم.
این هم نمونه جوابی که این تابع باید به ما بده:
{ "post": { "id": 2, "title": "A post from a request", "slug": "a-post-from-a-request", "body": "This post has been create using an API call, to show you how awesome MVC can get!", "created_at": "2021-03-23T17:37:55.502Z", "updated_at": "2021-03-23T17:37:55.502Z" }, "status": "success" }
اما خب، چطوری این داده رو میتونیم همینجا ببینیم؟! بدون این که از curl و … استفاده کنیم؟
ویوها – View
در بخش پیش، کنترلرها رو با دیدی که برای API و … داریم ساختیم. برای استفاده از ویو، یک بار دیگه یه کنترلر میسازیم:
rails generate controller posts
در این کنترلر این دو تابع رو قرار میدیم:
def index @posts = Post.all end def show @post = Post.find(params[:id]) end
خب قرار نبود این قسمت اینجاش با کد زدن شروع شه اما خب نیاز بود درک کنیم که ویو دقیقا چیه. خب، بیایم با دو تعریف آشنا شیم.
قابل خواندن برای ماشین (Machine Readable) : این مفهوم، به معنای دادههاییه که صرفا برای کامپیوتر قابل درک و خواندن هستن و برای کاربر، خواندنشون مشکله. مثل خروجی JSON یا چیزایی که تو دیتابیس ذخیره میکنیم. وقتی از روی API دادهای میفرستیم، در واقع دادهها رو به صورت قابل خواندن برای ماشین میفرستیم و دریافت میکنیم.
قابل خواندن برای انسان (Human Readable) : این مفهوم، به معنای دادههاییه که برای انسان قابل خوندن هستند. مثل همین پستی که شما دارید میخونید. در واقع این پست در یک دیتابیس MySQL به شکل عجیب و غریب (برای انسان) ذخیره شده و وقتی شما روی لینکش کلیک میکنید، به شکل قابل خواندن برای انسان درمیاد.
اگر صرفا API داشته باشیم، کار ما MVC نیست، اما میتونیم ویو رو با استفاده از Front End داشته باشیم (که در آینده در مورد اون هم یک مطلب خواهم نوشت). اما میتونیم بیایم و برای حداقل همون حرف R یک ویو بسازیم (در حقیقت دو تا). چرا؟ چون بعضی وقتا ممکنه برنامهنویس فرانت نداشته باشیم، بعضی وقتا ممکنه حتی نیازی به فرانت نباشه و … .
برای ساخت ویو در ریلز، نیازی به اجرای دستوری نیست. در این فرمورک، وقتی کنترلر بسازید در پوشه:
app/views
یک پوشه به اسم کنترلر مربوطه ساخته میشه. پس در پوشه:
app/views/posts
دو فایل به اسمهای index.html.erb و show.html.erb ایجاد میکنیم.
در فایل index.html.erb این موارد رو باید داشته باشیم:
<h1> Posts </h1> <% for post in @posts %> <ul> <li> <a href="/posts/<%= post.id %>"> <%= post.title %></a> </li> </ul> <% end %>
همونطور که میبینید، این کد دو تفاوت اساسی با HTML معمولی داره :
- پسوندش یه ERB اضافه داره
- کد روبی وسطش زده شده.
خب، این زبون، HTML نیست (بله، HTML هم زبانه، فقط «زبان برنامهنویسی» نیست!) بلکه زبان تمپلیتینگ خود روبیه. مثل blade در php یا Jinja در پایتون.
در show.html.erb هم این کد رو قرار میدیم:
<h1> <%= @post.title %> </h1> <h2> Slug: <%= @post.slug %> </h2 > <h2> Body: </h2> <p> <%= @post.body %> </p>
حالا اگر با مرورگر به localhost:3000/posts بریم این صحنه زشت رو میبینیم 😁
و اگر روی لینکها کلیک کنیم، وارد پستها میشیم:
البته یادتون باشه slug معمولا برای url کاربرد داره ولی چون اینجا قصد آموزش ریلز نبود، از این ماجرا صرف نظر کردیم.
خب، حالا بعد یک مطلب طولانی، میدونیم که چی به چیه؛ بیاید با هم جمعبندی کنیم.
جمعبندی مطلب
اول از همه در این مطلب به یک تعریفی برای MVC رسیدیم. در واقع، کار ما بعنوان یک مهندس نرمافزار، تبدیل مفاهیم و موجودیتهای یک بیزنس و روابطشون با هم به یک سری مدل، کنترلر و نحوه نمایششون به کاربر در فرم ویو خواهد بود. حالا که یک تعریفی از MVC داریم، به نظرم بد نیست که بیایم هر مورد هم یک بار دیگر، بررسی کنیم.
- مدل : مدلها، تعاریف انتزاعی از موجودیتها، ویژگیها و روابط بینشون هستند. به عبارتی، شکلی که ما قراره داده خام رو پردازش کنیم، در مدل تعیین میشه. کمی راحتترش کنم؟ مدل در واقع ساختار دیتابیس ماست.
- کنترلر : کنترلر وظیفه ساخت یک نمونه از مدل، استخراج داده از دیتابیس، بروزرسانی داده در دیتابیس و حذف یک رکورد از دیتابیس رو عهده داره. این هم اگر بخوام راحت توصیف کنم، میگم ابزاری که باهاش دیتابیس رو به شکل سادهتری کنترل میکنیم.
- ویو : ویو، نتیجه یک کوئری رو به صورت قابل خواندن توسط انسان، به ما نمایش میده. نتیجه کوئری معمولا به صورت یک جدول در دیتابیس (در واقع یک یا چند سطرش) یا به صورت JSON برمیگرده که توسط انسان قابل خوندن نیست. ما در ویو (و البته اغلب فرانت) این اتفاق رو رقم میزنیم.
البته موارد دیگری هم هستند که دیگه واقعا از حوصله این مطلب خارج شدند. دلیل عمدهش؟ طولانی شدن بیش از حد این مطلب. وگرنه دوست داشتم از معایب و مزایای MVC هم حرف بزنم. یک اتفاق بامزه هم این وسط افتاد که من حتی مجبور شدم لپتاپ رو ریبوت کنم و شانس آوردم که دو سه ساعت فکر و نوشتن، نپریده بود 😂
در پایان، از همه شمایی که این مطلب رو خوندید تشکر میکنم که وقت گذاشتید بابت خوندن مطلب. ضمنا، این اتفاقات در فرمورکی که شما استفاده میکنید چطوری رقم میخوره؟ در نظرات منتظرتونم 🙂