ساخت سیستم ردگیری فیلم با ری‌اکت و جاوا (Vert.x) — از صفر تا صد

۹۵ بازدید
آخرین به‌روزرسانی: ۰۵ شهریور ۱۴۰۲
زمان مطالعه: ۳۰ دقیقه
ساخت سیستم ردگیری فیلم با ری‌اکت و جاوا (Vert.x) — از صفر تا صد

جاوا یکی از زبان‌های برنامه‌نویسی قدرتمند است که سال‌ها است در عرصه حضور دارد. استفاده از آن در طی زمان افزایش یافته است و جامعه جاوا همچنان در حال ساخت پروژه‌های هیجان‌انگیز و جذاب است. یکی از این پروژه‌ها Vert.x است. این پروژه یک کیت ابزار برای ساخت اپلیکیشن‌های واکنشی روی JVM است. اغلب افرادی که سال‌ها تجربه کار روی سیستم‌های سازمانی جاوا داشته‌اند، هنگام کار با Vert.x متوجه خواهند شد گه استفاده از این فریمورک برای توسعه همه انواع مختلف اپلیکیشن‌ها روشی جالب و مفرح محسوب می‌شود. در این مقاله با روش ساخت سیستم ردگیری فیلم با ری‌اکت و جاوا آشنا خواهیم شد.

در سوی دیگر ماجرا جاوا اسکریپت قرار دارد که قدمتی تقریباً به همان اندازه جاوا دارد و در طی زمان رشد یافته است. ساخت رابط‌های کاربری مبتنی بر وب با استفاده از قابلیت‌های جدیدی که به خانواده جاوا افزوده شده یعنی Reac.js کاری جالب محسوب می‌شود.

در این مقاله تلاش می‌کنیم تا یک وب‌سایت برای ردگیری فیلم‌هایی بسازیم که دوست داریم تماشا کنیم و به این منظور از هر دو فریمورک React.js در فرانت‌اند و Vert.x در بک‌اند استفاده می‌کنیم. در نتیجه پروژه‌ای داریم که هم آسان است و هم به خوبی در کنار هم ترکیب می‌شود. در ادامه با روش انجام کار آشنا خواهیم شد.

توضیح خلاصه‌ای از پروژه

ابتدا کمی در مورد پروژه توضیح می‌دهیم. فرض کنید خانواده‌ای از تماشای فیلم در کنار هم لذت می‌برند و هر شب برای دیدن فیلم کنار هم جمع می‌شوند، اما مشکلی که وجود دارد غالباً در مورد انتخاب فیلمی است که همه روی آن توافق داشته باشند. البته مشکل این نیست که چنین فیلم‌هایی کم هستند، بلکه مشکل این جا است که همه فکر می‌کنند این فیلم‌ها فعلاً مناسب تماشا نیستند و شاید در آینده بهتر است آن‌ها را ببینیم. بنابراین هدف از این پروژه آن است که همه فیلم‌های جالبی که هنوز ندیده‌ایم را ردگیری کنیم.

در این مقاله تلاش می‌کنیم که همه چیز ساده بماند. به این جهت بسیاری از خصوصیاتی که یک فیلم را تشکیل می‌دهند حذف کرده و صرفاً به نام و ژانر فیلم بسنده می‌کنیم. همچنین از ذخیره داده‌ها در یک پایگاه داده نیز اجتناب کرده و آن‌ها را صرفاً در حافظه حفظ می‌کنیم.

ساختارهای مختلفی وجود دارند که در زمان ایجاد اپلیکیشن‌های ری‌اکت می‌توان انتخاب کرد و مورد استفاده قرار داد. ما همه این ساختارها را عامدانه کنار می‌گذاریم، چون موجب می‌شوند که افراد تازه‌کار از درک ماهیت ری‌اکت دور شوند.

ضمناً با این که این روزها همه از معماری میکروسرویس استفاده می‌کنند، در این پروژه از آن استفاده نمی‌کنیم. به جای آن از یک روش ساده برای طراحی یک وب‌اپلیکیشن خودبسنده بهره می‌گیریم. اگر شما به معماری مبتنی بر میکروسرویس علاقه‌مند هستید، می‌توانید این پروژه را به عنوان یک سرویس سطح بالا در نظر بگیرید که به نوبه خود با سرویس داده در سطح پایین‌تر ارتباط می‌گیرد.

در تصویر زیر ساختار اپلیکیشن را می‌بینید. یک اپلیکیشن ری‌اکت داریم که UI ما را نمایش می‌دهد. این اپلیکیشن با اپلیکیشن Vert.x صحبت می‌کند که به نوبه خود داده‌ها را اعتبارسنجی کرده و نگهداری می‌کند. هر دو اپلیکیشن در یک فایل اجرایی منفرد بسته‌بندی می‌شوند که به طور معمول یک فایل Java Archive یا JAR است.

Vert.xتوضیح در خصوص اجزای سازنده

اغلب خوانندگان این مقاله احتمالاً با یکی از موارد جاوا یا Node.js و اکوسیستم آن‌ها آشنا هستند. اما با این حال ما این موارد را اجمالاً توضیح می‌دهیم.

Node.js

نود. جی‌اس یک محیط زمان اجرا برای جاوا اسکریپت است که بر مبنای موتور جاوا اسکریپت مرورگر کروم یعنی V8 ساخته شده است. این محیط به طور عمده برای طراحی وب‌اپلیکیشن‌های جاوا اسکریپت در سمت سرور استفاده می‌شود، اما کاربردهای آن محدود به این نیست. امکان اجرای نرم‌افزار نامرتبط با وب نیز بر روی Node.js وجود دارد. در این مقاله ما از آن صرفاً برای کمک به بسته‌بندی و تست UI وب خود استفاده می‌کنیم.

Npm

این کلمه اختصاری برای عبارت «مدیریت بسته Node» (Node Package Manager) است که ابزار اصلی برای مدیریت برنامه‌های Node محسوب می‌شود. Npm ابزاری است که همراه با Node.js نصب می‌شود و با ریپازیتوری پکیج npm ارتباط می‌گیرد.

Java

جاوا در این مقاله به دو معنا است، یعنی هم به زبان برنامه‌نویسی جاوا گفته می‌شود و هم یک پلتفرم است که به طور عمده از یک کامپایلر و یک محیط زمان اجرا (ماشین مجازی جاوا یا JVM) تشکیل یافته است. این روزها دو توزیع عمده از جاوا وجود دارد که یکی Oracle Java و دیگری OpenJDK است. با توجه به مشکلات بالقوه لایسنس توزیع Oracle اغلب توسعه‌دهندگان امروزه از توزیع OpenJDK استفاده می‌کنند.

Maven

ماون یکی از رایج‌ترین ابزارهای مدریت پکیج و سیستم بلد است که در اکوسیستم جاوا مورد استفاده قرار می‌گیرد. Maven را می‌توان از طریق ابزار خط فرمان به نام mvn مورد استفاده قرار داد. همچنین به خوبی با دیگر IDE-های رایج جاوا ترکیب می‌شود.

Maven Central ریپازیتوری مرکزی است که پکیج‌های ماون در آن ذخیره می‌شوند.

به طور مشابه می‌توان توضیحات زیادی در خصوص React.js و Vert.x نوشت، اما در این جا به توضیح اجمالی بسنده می‌کنیم.

React.js

یک کتابخانه برای ساخت اینترفیس‌های کاربری قدرتمند و تعاملی گفته می‌شود. غالباً به صورت حرف V در معماری MVC اپلیکیشن‌ها توصیف می‌شود. MVC اختصاری برای عبارت «مدل/نما/کنترلر» (Model/View/Controller) است. به این ترتیب ری‌اکت غالباً وظیفه بخش نما را بر عهده دارد.

Vert.x

یک کیت ابزار برای ساخت اپلیکیشن‌های واکنشی روی JVM است. همانند Node.js مبتنی بر حلقه رویداد است و از این رو ابزارهایی ارائه می‌کند که نوشتن کد غیر مسدودکننده را تسهیل می‌کنند. Vert.x مزیت‌های دیگری از قبیل مدل actor و bus رویداد را نیز علاوه بر محیط توسعه polyglot ارائه می‌کند.

در جدول زیر مؤلفه‌های هر اکوسیستم مقایسه شده‌اند:

Vert.x

گردآوری اجزا در کنار هم

در این بخش تلاش می‌کنیم اجزایی که در بخش قبل توضیح دادیم را گرد هم جمع کنیم.

Node.js و npm

نخستین چیزی که باید نصب کنیم Node.js و npm است. اغلب توسعه‌دهندگان جاوا اسکریپت از قبل این موارد را روی سیستم خود نصب دارند و از این رو می‌توانند این بخش را رد کنند. در غیر این صورت برای این که متوجه شوید Node.js و npm را روی سیستم نصب دارید یا نه می‌توانید در خط فرمان عبارت node –v را وارد کنید.

اگر این دستور پاسخ نداد، می‌توانید موارد گفته شده را به صورت زیر نصب کنید. به وب‌سایت نصب Node.js (+) بروید. جدیدترین نصاب را برای پلتفرم خود انتخاب کرده و اجرا نمایید. در ادامه دوباره دستور node –v یا npm-v را اجرا کنید تا از نصب شدن آن‌ها مطمئن شوید. این بار باید نسخه آن‌ها نمایش یابد.

توجه کنید که شماره نسخه این دو مورد ممکن است متفاوت باشد که مشکلی ایجاد نمی‌کند. همچنین یک برنامه دیگر به نام npx نیز ممکن است نصب شده باشد. Npx جهت تسهیل اجرای ابزارهای خط فرمان ارائه شده است که روی ریپوی npm میزبانی می‌شوند.

Java و Maven

در این راهنما باید Java و Maven را نیز نصب کنیم. ابتدا بررسی می‌کنیم که جاوا نصب شده است یا نه. به این منظور به خط فرمان بروید و دستور java –version را وارد کنید. اگر نصب باشد که تبریک می‌گوییم، در غیر این صورت باید آن را نصب کنید.

همچنان که پیش‌تر اشاره کردیم، این روزها OpenJDK به توزیع جاوا اوراکل ترجیح داده می‌شود. آسان‌ترین روش برای نصب OpenJDK این است که به وب‌سایت AdoptOpenJDK (+) بروید. نسخه‌ای از جاوا که می‌خواهید نصب کنید را انتخاب کنید و سپس نوع JVM را برگزینید. معمولاً آخرین گزینه جاوا و در مورد JVM نیز HotSpot بهتر است. سپس روی دکمه آبی بزرگ دانلود کلیک کنید تا نصاب دانلود شود. در ادامه نصاب را باز کنید و دستورالعمل‌ها را پیگیری نمایید.

Vert.x

پس از آن با وارد کردن دستور mvn –v نصب شدن Maven را بررسی کنید.

اگر این دستور پاسخ نداد، باید Maven را نیز نصب کنید. به این منظور به وب‌سایت دانلود Maven (+) بروید و یکی از فایل‌های باینری.tar.gz یا.zip را دانلود کنید. سپس با پیگیری دستورالعمل‌ها روی صفحه نصب Maven (+) آن را نصب کنید.

Vert.x

زمانی که کار پایان یافت، با وارد کردن دستور mvn –v باید شماره نسخه آن را ببینید.

ایجاد پروژه ری‌اکت

اینک به بخش جالب ماجرا می‌رسیم. ابتدا باید جایی برای قرار دادن اپلیکیشن ری‌اکت خود داشته باشیم. ساختار معمول عموماً به صورت زیر است:

movies/
      react-app/
      vertx/

بنابراین محل مناسبی برای آغاز پروژه انتخاب کنید، مثلاً دایرکتوری home یا Documents و یک دایرکتوری به نام movies/ بسازید و سپس به داخل آن cd کنید. برای نمونه روی سیستم‌های مک به صورت زیر عمل می‌کنیم:

$ cd ~/Documents
$ mkdir movies
$ cd movies

اسکریپت create-react-app

اکنون باید اپلیکیشن ری‌اکت خود را بسازیم. به این منظور از ابزاری به نام create-react-app کمک می‌گیریم. این ابزار از سوی فیسبوک ارائه شده است و روی npm میزبانی می‌شود. آن را با استفاده از کتابخانه npx که قبلاً اشاره کردیم، اجرا می‌کنیم:

npx create-react-app react-app

پس از یک یا دو دقیقه باید یک دایرکتوری فرعی به نام react-app/ داشته باشیم که شامل یک چارچوب کلی برای اپلیکیشن ری‌اکت است:

$ cd react-app
$ ls
README.md node_modules package.json public src yarn.lock

جالب‌ترین آیتم‌ها شامل دایرکتوری /public است که محتوی فایل‌های استاتیک از قبیل صفحه‌های HTML و آیکون‌ها است. دایرکتوری /src نیز شامل کدهای جاوا اسکریپت و CSS اپلیکیشن است. این محتواها را در ادامه بیشتر بررسی می‌کنیم. فعلاً اپلیکیشنی که برای ما ساخته شده است را اجرا می‌کنیم.

درون دایرکتوری /react-app دستور npm start را اجرا کنید تا npm کد را روی پورت پیش‌فرض 3000 اجرا کرده و مرورگر را برای ما باز کند. در طی چند ثانیه باید چیزی مانند تصویر زیر ببینید:

Vert.x

پیش از آن که به ادامه توضیحات بپردازیم، باید ویرایش سریعی روی فایل src/App.js اجرا کنیم که از سوی خود اپلیکیشن رندر شده توصیه شده است. برای نمونه محتوای <p> را به صورت زیر در می‌آوریم:

1<p>
2 I have edited <code>src/App.js</code> and will save to reload.
3</p>

فایل را ذخیره کنید و سپس بدون هیچ کار دیگری به مرورگر بازگردید. می‌بینید که تغییرات به صورت خودکار بازتاب یافته‌اند:

Vert.x

ایجاد فایل‌های پایه

به جای این که به بررسی فایل‌های آماده و قدرتمند ارائه شده بپردازیم، تلاش می‌کنیم فایل‌های خودمان را بسازیم. نخستین کاری که باید در این دایرکتوری‌ها انجام دهیم، حذف کردن اغلب فایل‌ها است. این کار در صورت استفاده از اسکریپت create-react-app برای ساخت اپلیکیشن ری‌اکت رویه معمولی به حساب می‌آید. به طوری که ابتدا اپلیکیشن را ایجاد می‌کنیم و سپس اغلب کدهای قالبی آماده را حذف می‌کنیم.

کار خود را از دایرکتوری /src آغاز می‌کنیم. در این جا همه فایل‌ها به جز فایل‌های index.css و index.js را حذف می‌کنیم. سپس به درون دایرکتوری /public می‌رویم و همه چیز به جز فایل index.htnl را پاک می‌کنیم.

سپس فایل public/index.html را باز می‌کنیم و محتوای موجود را با کد زیر عوض می‌کنیم:

1<!DOCTYPE html>
2<html lang="en">
3  <head>
4    <meta charset="utf-8" />
5    <title>Movies</title>
6  </head>
7  <body>
8    <div id="root"></div>
9  </body>
10</html>

سپس به دایرکتوری /src می‌رویم. در اینجا فایل index.js را باز می‌کنیم و محتوای آن را با کد زیر عوض می‌کنیم:

1import React from 'react';
2import ReactDOM from 'react-dom';
3import './index.css';
4import App from './App';
5ReactDOM.render(<App />, document.getElementById('root'));

در ادامه بررسی می‌کنیم که چه اتفاقاتی در حال رخ دادن است. زمانی که npm اپلیکیشنی را که مانند اپلیکیشن ما با استفاده از create-react-app ساخته شده است، کامپایل و اجرا می‌کند به دنبال دو فایل به نام‌های index.html و index.js می‌گردد. همه چیز از این دو فایل آغاز می‌شود. فایل index.html بسیار ابتدایی است. جالب‌ترین بخش آن خطی است که مشخص می‌کند هسته مرکزی اپلیکیشن ری‌اکت ما کجا قرار دارد:

1<div id="root"></div>

همچنین فایل index.js جایی است که اپلیکیشن عملاً بوت‌استرپ می‌شود. این اتفاق در خط زیر رخ می‌دهد:

1ReactDOM.render(<App />, document.getElementById('root'));

در این خط یک فراخوانی به ()ReactDOM.render انجام می‌دهیم که مشخص می‌کند چه چیزی باید رندر شود و کجا باید رندر شود. آن چه باید رندر شود یک وهله از کامپوننت App است که در بخش بعدی بیشتر بررسی می‌کنیم. محل رندر هم آن عنصر HTML است که با سلکتور ارائه شده زیر که در index.html تعریف شده است مشخص می‌شود:

1document.getElementById('root')

متوجه خواهید شد که چند ایمپورت بر مبنای index.js وجود دارند. استفاده از ایمپورت‌ها موجب می‌شود که کامپوننت‌ها یا ماژول‌های دیگر اپلیکیشن نیز به طرز مؤثری به کار گرفته شوند. این دو مورد نخست یعنی React و ReactDom به طور بدیهی ماژول‌هایی را از خود فریمورک ایمپورت می‌کنند. ایمپورت بعدی نیز فایل index.css است. این فایل را باز می‌کنیم و محتوای زیر را دران قرار می‌دهیم:

1body {
2 margin: 0;
3 font-family: 'Helvetica Neue', sans-serif;
4 -webkit-font-smoothing: antialiased;
5 -moz-osx-font-smoothing: grayscale;
6}

در نهایت این خط را داریم:

import App from './App'’';

این خط مشخص می‌کند که یک ماژول به نام App از فایلی به نام App.js در همان دایرکتوری ایمپورت می‌شود. توجه کنید که فایل index.js تعیین کرده است که یک وهله از کامپوننت app به جای عنصر زیر در فایل HTML رندر می‌شود:

document.getElementById(‘root’)

کامپوننت‌ها بلوک‌های سازنده اصلی هر اپلیکیشن ری‌اکت هستند. کامپوننت را می‌توان به عنوان یک عنصر UI تصور کرد که می‌تواند خودش را روی صفحه نمایش دهد، به تغییرات در یک مدل زیرین واکنش نشان دهد و موجب تغییراتی در مدل شود. ضمناً کامپوننت‌ها دارای قلاب‌های چرخه عمری هستند که می‌توان از آن‌ها نیز استفاده کرد و چنان که انتظار می‌رود کامپوننت‌ها می‌توانند درون کامپوننت‌های دیگر نیز جاسازی شوند.

در ادامه کامپوننت App خود را با باز کردن فایل app.js تعریف می‌کنیم و محتوای زیر را در آن قرار می‌دهیم:

1import React, { Component } from "react";
2import './App.css';
3class App extends Component {
4  ()render {
5    return (
6      <div className="App">
7          <h1>
8            Movies
9          </h1>
10      </div>
11      )
12    }
13  }
14export default App;

چندین نکته وجود دارند که باید توجه کنیم. نخستین نکته این است که این فایل یک ماژول App.js را ایمپورت می‌کند که شامل استایل‌های CSS است. منطق و استایل کامپوننت به طور معمول به این روش در اپلیکیشن‌های ری‌اکت ترکیب می‌شوند. بنابراین هر کامپوننت Foo رفتار خاص خود را دارد که در فایل Foo.js تعریف شده است و استایل آن نیز در فایل Foo.css قرار دارد.

در ادامه یک استایل ساده برای کامپوننت App خود درون فایل App.css ایجاد می‌کنیم:

1.App {
2  text-align: center;
3  background-color: #fff;
4  min-height: 100vh;
5  display: flex;
6  flex-direction: column;
7  align-items: center;
8  justify-content: center;
9  font-size: calc(10px + 2vmin);
10}
11h1 {
12  color: #666666;
13}
14.App-link {
15  color: #61dafb;
16}

علاوه بر این ایمپورت‌ها، اغلب بخش‌های App.js شامل اعلان کردن یک شیء App است که react.Component را بسط می‌دهد. با اجرای این کلاس چند تابع ارائه می‌کنیم که می‌توانند override شوند. شاید مهم‌ترین بخش این فایل، تابع ()render باشد. در این تابع یک markup بازگشت می‌دهیم که شیوه رندر شدن کامپوننت را روی صفحه تعیین می‌کند. کامپوننت App مارکاپ زیر را بازگشت می‌دهد:

1<div className="App">
2  <h1>
3    Movies
4  </h1>
5</div>

کسانی که تجربه قبلی از کار فرانت‌اند دارند، ممکن است این نشانه‌گذاری را HTML تشخیص دهند، اما تصور نادرستی است. با این که ظاهر آن شبیه HTML است، اما در عمل یک زبان دیگر به نام JSX است. JSX به ما امکان می‌دهد که نشانه‌گذاری را به روشی آسان با جاوا اسکریپت ترکیب کرده و عناصر UI را بسازیم.

با این که JSX در اغلب موارد شبیه HTML به نظر می‌رسد، اما تفاوت‌های ظریفی دارد. در عمل یک سرنخ در قطعه کد فوق وجود دارد که در HTML معمولی با آن کار نمی‌کنیم و آن تگ className در div است. اگر این کد HTML خالص بود، یک App را به صورت class اعلان کرده بودیم؛ اما از آنجا که class یک کلیدواژه جاوا اسکریپت است در واقع به جای آن از تگ className استفاده کرده‌ایم.

اینک بار دیگر اپلیکیشن خود را اجرا می‌کنیم تا ببینیم کجا هستیم:

$ npm start

بدین ترتیب با صفحه ساده زیر مواجه می‌شویم:

Vert.x

در بخش بعدی با روش نمایش برخی فیلم‌ها آشنا می‌شویم.

نمایش فیلم‌ها

ابتدا باید کامپوننت‌هایی که قرار است ساخته شوند را مشخص سازیم. ما سه کامپوننت داریم که هر یک با رنگ متمایزی در تصویر زیر ارائه شده است.

Vert.x

کامپوننت آبی نماینده کامپوننت App است که قبلاً ساختیم. دو کامپوننت دیگر نیز به شرح زیر اضافه خواهیم کرد:

  • MovieForm – به رنگ نارنجی نمایش یافته است و به ما امکان می‌دهد که فیلم جدیدی در لیست اضافه کنیم.
  • MovieList – به رنگ سبز نمایش یافته و فیلم‌های لیست را نمایش می‌دهد.

افزودن فیلم به نمایش لیست

در این بخش شروع به ایجاد دو فایل در دایرکتوری می‌کنیم که MovieList.js و MovieList.css نام دارند. فایل دوم باید شامل محتوای زیر باشد:

1.movie-list {
2  border-collapse: collapse;
3  margin-top: 20px;
4  width: 100%;
5}
6
7table, th, td {
8  border: 1px solid black;
9}
10
11th, td {
12  padding: 8px;
13}

فایل MovieList.js نیز شامل کد زیر است:

1import React, { Component } from "react";
2import './MovieList.css'
3
4class MovieList extends Component {
5
6    constructor(props) {
7        super(props);
8        this.toMovie = this.toMovie.bind(this);
9        this.genreLabel = this.genreLabel.bind(this);
10    }
11	
12    genreLabel(g) {
13        return "?";
14    }
15	
16    toMovie(m) {
17        var g = "?";
18        for (var i = 0; i < this.props.genres.length; i++) { 
19            if (this.props.genres[i].value == m.genre) {
20                g = this.props.genres[i].label;
21                break;
22            }
23        } 
24	return (<tbody key={m.guid}><tr><td>{m.title}</td><td>{g}</td></tr></tbody>)
25    }
26
27    ()render {
28        return (
29            <table className="movie-list" >
30	    <tbody>
31            <tr>
32                <th>Name</th>
33                <th>Genre</th>
34            </tr>
35            </tbody>
36            {this.props.movies.map(this.toMovie)}
37            </table>
38        )
39    }
40
41}
42
43export default MovieList;

سازنده‌ها

نخستین نکته جالبی که متوجه می‌شویم این است که MovieList سازنده پیش‌فرض برای Component را override می‌کند. پارامتری به نام props به سازنده ارسال می‌شود. ما این پارامتر را در ادامه بیشتر بررسی می‌کنیم، اما توجه داشته باشید که به سازنده سوپرکلاس ارسال می‌شود. بدین ترتیب مقدار یک متغیر در سطح کلاس props تعیین می‌شود.

همچنین یک تابع می‌بینید که به وهله جاری App پیوند یافته است:

this.toMovie = this.toMovie.bind(this);

بدین ترتیب تابع toMovie می‌تواند از هر context از قبیل رویدادهای UI فراخوانی شود. این روش برای bind() کردن تابع‌ها در اپلیکیشن‌های ری‌اکت کاملاً مرسوم است.

رندر کردن UI

همانند اغلب کامپوننت‌ها، تابع ()render جایی است که اتفاقات جالبی رخ می‌دهند. هر زمان که ری‌اکت تشخیص دهد کامپوننت‌ها باید در UI مجدداً رندر شوند، تابع ()render مربوط به هر کامپوننت فراخوانی می‌شود.

در تابع ()render مربوط به MovieDisplay یک جدول ایجاد می‌کنیم. به خاطر داشته باشید که گرچه به نظر می‌رسد از HTML استفاده می‌کنیم، اما در عمل JSX می‌نویسیم. این بدان معنی است که تفاوت‌های اندکی وجود دارند که باید از آن‌ها آگاه باشید. به مثال زیر توجه کنید:

{this.props.movies.map(this.toMovie)}

یک جدول ایجاد می‌کنیم که برای پر کردن آن با مقادیر مختلف از جاوا اسکریپت کمک می‌گیریم.

پیش‌تر به شیء props اشاره کردیم که به این سازنده کامپوننت ارسال می‌شود. چنان که در ادامه خواهیم دید، یک آرایه از فیلم‌ها را به عنوان بخشی از شیء props ارائه می‌کنیم. بنابراین با استفاده از ()map روی آرایه‌ای که toMovie را به صوت تابع نگاشت ارسال کردیم، حلقه‌ای تعریف می‌کنیم. سپس toMovie یک شیء می‌گیرند و markup به شکل JSX مورد نیاز برای نمایش ردیف جدول را بازگشت می‌دهد:

1toMovie(m) {
2  var g = "?";
3  for (var i = 0; i < this.props.genres.length; i++) { 
4      if (this.props.genres[i].value == m.genre) {
5          g = this.props.genres[i].label;
6          break;
7      }
8  } 
9  return (<tbody key={m.guid}><tr><td>{m.title}</td><td>{g}</td></tr></tbody>)
10 }

نکته: آن تگ key یک الزام ری‌اکت است. هر آیتم یکتای UI در لیست باید برای خود کلیدی داشته باشد که آن را به صورت منحصر به فرد شناسایی می‌کند. ما به فیلم‌های خود GUID-هایی انتساب می‌دهیم تا از آن‌ها به عنوان کلیدهای یکتا استفاده کنیم.

در ادامه این کامپوننت را به کامپوننت App اضافه می‌کنیم تا ببینیم چه چیزی به دست می‌آوریم. درون App.js ابتدا MovieList را ایمپورت می‌کنیم:

1import MovieList from "./MovieList"

درست زیر گزاره‌های ایمپورت، یک آرایه از ژانرهای فیلم می‌سازیم:

1const genres = [ 
2  {value: 'action', label: 'Action'}, 
3  {value: 'comedy', label: 'Comedy'}, 
4  {value: 'drama', label: 'Drama'}, 
5  {value: 'thriller', label: 'Thriller'}, 
6  {value: 'musical', label: 'Musical'} 
7]

سپس یک آرایه از فیلم‌های نمونه می‌سازیم که باید نمایش یابند:

1const movies = [ 
2  {genre: 'action', title: 'Captain Marvel', guid: '6530b64b-0753-4629-a1bb-6716109b964b'}, 
3  {genre: 'comedy', title: 'Groundhog Day', guid: 'ba5b9881-7128-485f-84d5-afc50f199b23'}, 
4  {genre: 'action', title: 'Midway', guid: '2e93da48-d451-4df0-b77c-41dddde428ad'}, 
5  {genre: 'drama', title: 'Dances With Wolves', guid: 'f207c1a0-3bef-48f1-a596-29b84887e94d'}, 
6  {genre: 'thriller', title: 'Scream', guid: '3733f942-6a44-4eb9-af54-586d9d15eb67'} 
7]

در نهایت درون تابع ()render زیر هدر Movies یک وهله از MovieList اضافه می‌کنیم و ژانرها و فیلم‌ها را به آرگومان props سازنده MovieList اضافه می‌کنیم:

1<h1>
2  Movies
3</h1>
4<div>
5  <MovieList movies={movies} genres={genres} />
6</div>

سپس به مرورگر خود بازمی‌گردیم تا ببینیم چه چیزی حاصل شده است:

Vert.x

افزودن مدخل فیلم از طریق فرم

اکنون باید امکان افزودن فیلم‌های جدید را به فرم خود اضافه کنیم. کار خود را با ایجاد فایل‌های MovieForm.js و MovieForm.css در دایرکتوری src/ آغاز می‌کنیم. فایل دوم باید شامل کد زیر باشد:

1.movie-form {
2	border: 1px solid #666;
3	padding: 8px;
4	background-color: #aaa;
5}
6
7.movie-form-element {
8	border: 1px solid #666;
9	padding: 5px;
10	background-color: #fff;
11	margin-left: 3px;
12	margin-right: 3px;
13	margin-top: 2px;
14	margin-bottom: 2px;
15	padding: 8px;
16}
17
18.movie-form-element label {
19	color: black;
20	font-size: 14px;
21}
22
23.movie-form-element input {
24	color: black;
25	font-size: 12pt;
26	margin: 2px;
27}
28
29.movie-form-element select {
30	color: black;
31	font-size: 12pt;
32	margin: 2px;
33}

فایل MovieForm.js نیز محتوای کد زیر خواهد بود:

1import React, { Component } from "react";
2import './MovieForm.css'
3
4class MovieForm extends Component {
5
6    constructor(props) {
7        super(props);
8
9        this.handleChangeTitle = this.handleChangeTitle.bind(this);
10        this.handleChangeGenre = this.handleChangeGenre.bind(this);
11        this.changeState = this.changeState.bind(this);
12        this.toGenreOption = this.toGenreOption.bind(this);
13        
14        this.state = { title: '', genre: 'comedy' };
15    }
16
17    handleChangeTitle(event) {
18       this.changeState( { title: event.target.value } )
19    }
20
21    handleChangeGenre(event) {
22        this.changeState( { genre: event.target.value } )
23    }
24
25    changeState(keyVal) {
26        this.setState( Object.assign({}, this.state, keyVal) )
27    }
28
29    toGenreOption(g) {
30        return (<option key={g.value} value={g.value}>{g.label}</option>)
31    }
32
33    ()render {
34        return (
35            <>
36                <form className="movie-form" onSubmit={this.tryCreateMovie}>
37                <span className="movie-form-element">
38                    <label>Title 
39                    <input type="text" value={this.state.title} onChange={this.handleChangeTitle} />
40                    </label>
41                </span>
42                <span className="movie-form-element">
43                    <label>Genre 
44                    <select value={this.state.genre} onChange={this.handleChangeGenre}>
45                        {this.props.genres.map(this.toGenreOption)}
46                    </select>
47                    </label>
48                </span>
49                <span className="movie-form-element">
50                    <input type="submit" value="Submit" />
51                </span>
52            </form>
53            </>
54        )
55    }
56}
57
58export default MovieForm;

نگهداری حالت

درست مانند MovieList، فایل MovieForm نیز سازنده پیش‌فرض را باطل می‌کند و یک پارامتر props می‌گیرد. همچنین چند تابع به وهله جاری MovieForm اتصال می‌دهد:

1this.handleChangeTitle = this.handleChangeTitle.bind(this);

اما برخلاف MovieList در سازنده MovieForm یک شیء state برای کامپوننت ایجاد می‌کنیم. در این جا state صرفاً نشان‌دهنده فیلمی است که در حال حاضر ایجاد می‌کنیم. هنگامی که کامپوننت بارگذاری می‌شود، حالت آن یک شیء خواهد بود که فیلمی را با یک عنوان و ژانر خالی نمایش می‌دهد.

در هر کامپوننت آن State که به متغیر state انتساب می‌یابد باید تغییرناپذیر باشد. یعنی با این که از نظر فنی می‌توانید یک فراخوانی به صورت زیر داشته باشید:

1this.state.title = 'Dances With Wolves'

اما این کار اصلاً توصیه نمی‌شود. این کار موجب می‌شود که UI نتواند برای نمایش و بازتاب حالت جدید به‌روزرسانی شود. به جای آن باید چیزی مانند زیر را فراخوانی کنیم:

1this.setState({ title: 'Dances With Wolves', genre: this.state.genre })

از آنجا که setState را اجرا کرده‌ایم، UI باید کامپوننت را رندر مجدد بکند تا عنوان جدید را نشان دهد. ممکن است فکر کنید که ایجاد مجدد شیء state به این روش ممکن است شرایط پیچیده‌ای پدید آورد. البته به خصوص در مورد اشیای بزرگ‌تر حق با شما است. به همین جهت است که تابع ()changeState را ایجاد کرده‌ایم:

1changeState(keyVal) {
2  this.setState( Object.assign({}, this.state, keyVal) ) 
3}

این متد از تابع ()Object.assign بهره می‌گیرد که اساساً همه پارامترهای ارسالی را ادغام می‌کند. این موارد شامل یک شیء خالی، حالت جاری کامپوننت و تغییراتی است که می‌خواهیم در آن ایجاد شود. سپس یک شیء جدید بازگشت می‌یابد که حالت جدید را نشان می‌دهد. برای نمونه اگر حالت جاری به صورت زیر باشد:

1{ title: 'Dances With Wolves', genre: 'Action' }

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

1changeState({ genre: 'Drama' })

در این صورت حالت جدید به صورت زیر خواهد بود:

1{ title: 'Dances With Wolves', genre: 'Drama' }

در این جا دو دستگیره به نام‌های ()handleChangeGenre و ()handleChangeTitle داریم که از تابع ()changeState استفاده می‌کنند.

اتصال حالت به UI

اکنون زمان آن رسیده است که تابع ()render را بررسی کنیم. همچنان که پیش‌تر اشاره کردیم، استفاده از JSX موجب می‌شود که جاوا اسکریپت به صورت آسان‌تری با لی‌آوت ترکیب شود. به مثال زیر توجه کنید:

1<input type="text" value={this.state.title} onChange={this.handleChangeTitle} />

در این کد یک فیلد متنی ساده ایجاد می‌کنیم. اما به سادگی می‌توان به حالت کامپوننت MovieForm اتصال یافته و مقدار فیلد متنی را ارائه کرد. به این منظور کافی است یک دستگیره رویداد به نام handleChangeTitle به فیلد متنی اضافه کنیم. به بیان دیگر هر بار که متن فیلد رندر می‌شود، مقدار آن با state.title تطبیق می‌یابد. هر زمان هم که مقدار تغییر یابد، یعنی کاربر کاراکتری در فیلد متنی وارد کند دستگیره رویداد handleChangeTitle فراخوانی می‌شود و حالت به‌روزرسانی می‌شود.

همچنین از جاوا اسکریپت برای ایجاد یک ویجت select استفاده می‌کنیم که به وسیله آن از میان ژانرها انتخاب می‌کنیم:

1<select value={this.state.genre} onChange={this.handleChangeGenre}>
2  {this.props.genres.map(this.toGenreOption)} 
3</select>

در این کد یک ویجت انتخاب استاندارد HTML ایجاد می‌کنیم و از جاوا اسکریپت بهره می‌گیریم تا گزینه‌های آن را ارائه کنیم همان‌طور که در مورد MovieList عمل کردیم در این مورد نیز یک آرایه از ژانرها به شیء props ارائه می‌کنیم که به سازنده کامپوننت عرضه می‌شود. بنابراین با استفاده از ()map روی آرایه یک حلقه تعریف می‌کنیم و سپس تابع toGenreOption را به عنوان تابع نگاشت ارسال خواهیم کرد. سپس toGenreOption یک شیء ژانر می‌گیرد و نشانه‌گذاری JSX مورد نیاز برای نمایش آیتم select را بازگشت می‌دهد:

1return (<option key={g.value} value={g.value}>{g.label}</option>)

در ادامه این کامپوننت را به کامپوننت App اضافه می‌کنیم تا ببینیم چه به دست می‌آید. درون App.js ابتدا MovieForm را ایمپورت می‌کنیم:

1import MovieForm from "./MovieForm"

سپس درون تابع ()render زیر هدر Movies یک وهله از MovieForm اضافه می‌کنیم و ژانرها را به آرگومان props سازنده MovieForm ارسال می‌کنیم:

1<h1>
2  Movies
3</h1>
4<div>
5  <MovieForm genres={genres} />
6</div>

در ادامه به مرورگر خود بازمی‌گردیم تا ببینیم چه حاصل شده است:

Vert.x

ایجاد سرویس بک‌اند فیلم

تا به این جا فرم فیلم ما عملاً مقصود خاصی را برآورده نمی‌سازد. این وضعیت را در ادامه تغییر می‌دهیم، اما قبل از آن باید سرویس فیلم را در بک‌اند با استفاده از Vert.x بسازیم.

ساختار Vert.x

اینک زمان تنظیم اپلیکیشن Vert.x فرا رسیده است. اگر با یک IDE مانند IntelliJ یا Eclipse کار می‌کنید، می‌توانید از آن برای تولید خودکار یک پروژه Maven بهره بگیرید. ما در این راهنما این کار را به صورت دستی انجام می‌دهیم. در دایرکتوری سطح بالا در کنار دایرکتوری react-app، یک دایرکتوری به نام vertx ایجاد می‌کنیم. درون آن ساختار زیر را می‌سازیم:

├── pom.xml
├── src/
│   ├── main/
│   │   └── java/
│   │   └── resources/

این ساختار پیش‌فرض دایرکتوری برای یک پروژه کوچک Mavem محسوب می‌شود. src/main/java چنان که احتمالاً حدس می‌زنید، شامل کد سورس جاوای پروژه است. src/main/resources شامل منابعی است که درون اپلیکیشن جاوا بسته‌بندی شده‌اند. به طور کلی ما باید دایرکتوری‌های src/test/java و src/test/resources را نیز داشته باشیم، اما به منظور ساده‌تر ماندن این موارد را نادیده می‌گیریم.

فایل pom.xml فایلی است که به Maven اعلام می‌کند ساختار پروژه چگونه باشد. اغلب بخش‌های این فایل به منظور پیکربندی Vert.x استفاده می‌شود. محتوای زیر را در فایل pom.xml کپی کنید:

1<?xml version="1.0" encoding="UTF-8"?>
2<project xmlns="http://maven.apache.org/POM/4.0.0"
3	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5	<modelVersion>4.0.0</modelVersion>
6
7	<groupId>com.me</groupId>
8	<artifactId>movies-app</artifactId>
9	<version>1.0.0-SNAPSHOT</version>
10
11	<properties>
12		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13
14		<maven.compiler.source>1.8</maven.compiler.source>
15		<maven.compiler.target>1.8</maven.compiler.target>
16
17		<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
18		<maven-shade-plugin.version>2.4.3</maven-shade-plugin.version>
19		<maven-surefire-plugin.version>2.22.1</maven-surefire-plugin.version>
20		<exec-maven-plugin.version>1.5.0</exec-maven-plugin.version>
21
22		<vertx.version>3.8.1</vertx.version>
23		<junit-jupiter.version>5.4.0</junit-jupiter.version>
24
25		<main.verticle>com.me.Main</main.verticle>
26	</properties>
27
28	<dependencyManagement>
29		<dependencies>
30			<dependency>
31				<groupId>io.vertx</groupId>
32				<artifactId>vertx-stack-depchain</artifactId>
33				<version>${vertx.version}</version>
34				<type>pom</type>
35				<scope>import</scope>
36			</dependency>
37		</dependencies>
38	</dependencyManagement>
39
40	<dependencies>
41		<dependency>
42			<groupId>io.vertx</groupId>
43			<artifactId>vertx-core</artifactId>
44		</dependency>
45		<dependency>
46			<groupId>io.vertx</groupId>
47			<artifactId>vertx-web</artifactId>
48		</dependency>
49		<dependency>
50			<groupId>org.apache.commons</groupId>
51			<artifactId>commons-lang3</artifactId>
52			<version>3.9</version>
53		</dependency>
54	</dependencies>
55
56	<build>
57		<plugins>
58			<plugin>
59				<artifactId>maven-compiler-plugin</artifactId>
60				<version>${maven-compiler-plugin.version}</version>
61			</plugin>
62			<plugin>
63				<artifactId>maven-shade-plugin</artifactId>
64				<version>${maven-shade-plugin.version}</version>
65				<executions>
66					<execution>
67						<phase>package</phase>
68						<goals>
69							<goal>shade</goal>
70						</goals>
71						<configuration>
72							<transformers>
73								<transformer
74									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
75									<manifestEntries>
76										<Main-Class>io.vertx.core.Launcher</Main-Class>
77										<Main-Verticle>${main.verticle}</Main-Verticle>
78									</manifestEntries>
79								</transformer>
80								<transformer
81									implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
82									<resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource>
83								</transformer>
84							</transformers>
85							<artifactSet>
86							</artifactSet>
87							<outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
88							</outputFile>
89						</configuration>
90					</execution>
91				</executions>
92			</plugin>
93			<plugin>
94				<artifactId>maven-surefire-plugin</artifactId>
95				<version>${maven-surefire-plugin.version}</version>
96			</plugin>
97			<plugin>
98				<groupId>org.codehaus.mojo</groupId>
99				<artifactId>exec-maven-plugin</artifactId>
100				<version>${exec-maven-plugin.version}</version>
101				<configuration>
102					<mainClass>io.vertx.core.Launcher</mainClass>
103					<arguments>
104						<argument>run</argument>
105						<argument>${main.verticle}</argument>
106					</arguments>
107				</configuration>
108			</plugin>
109		</plugins>
110	</build>
111
112
113</project>

می‌توانید groupId، artifactId و/یا version را در صورت تمایل عوض کنید. بقیه موارد می‌توانند دست‌نخورده بمانند.

Verticle و کنترلرهای Vert.x

اکنون نوبت به نوشتن مقداری کد رسیده است. Vert.x نوعی مدل Actor ارائه می‌کند که از کامپوننت‌های مستقلی به نام verticle تشکیل یافته است. این ابزار قدرتمندی است که می‌توانیم برای رفع نیازهای کوچک یا بزرگ خود مورد استفاده قرار دهیم. در این پروژه یک Verticle به عنوان بخش اجراکننده اپلیکیشن می‌سازیم.

در دایرکتوری src/main/java یک پکیج به نام com.me (یا می‌توانید از نام دامنه خود استفاده کنید) ایجاد کنید. این بدان معنی است که زیرپوشه‌های com/me را ایجاد می‌کنیم.

در این بخش به بررسی ساختار دایرکتوری می‌پردازیم که باید مانند زیر باشد:

├── react-app
│   (stuff)
├── vertx
│   ├── pom.xml
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/
│   │   │   │   ├── com/
│   │   │   │   │   └── me/
│   │   │   └── resources/

در دایرکتوری جدید /new فایل Main.java را با محتوای زیر اضافه می‌کنیم:

1package com.me;
2import io.vertx.core.AbstractVerticle;
3
4public class Main extends AbstractVerticle {
5  
6  @Override
7  public void start() throws Exception {
8    System.out.println("Movie app starting...");
9    new AppServer().run(vertx);
10  }
11  
12}

در همان دایرکتوری یک فایل به نام AppServer.java با محتوای زیر ایجاد می‌کنیم:

1package com.me;
2
3import io.vertx.core.Vertx;
4import io.vertx.core.http.HttpServer;
5import io.vertx.core.http.HttpServerResponse;
6import io.vertx.core.logging.Logger;
7import io.vertx.core.logging.LoggerFactory;
8import io.vertx.ext.web.Router;
9import io.vertx.ext.web.handler.StaticHandler;
10
11public class AppServer {
12
13    private final Logger LOGGER = LoggerFactory.getLogger( AppServer.class );
14
15    public void run(Vertx vertx) {
16        HttpServer server = vertx.createHttpServer();
17
18        Router router = Router.router(vertx);
19
20        router.get("/movies").handler(ctx -> {
21            HttpServerResponse resp = ctx.response();
22            resp.end("No movies!"); 
23        });
24        
25        router.post("/movies").handler(ctx -> {
26            HttpServerResponse resp = ctx.response();
27            resp.end("Unable to save movies"); 
28        });
29
30        server.requestHandler(router).listen(80, http -> {
31            if (http.succeeded()) {
32                LOGGER.info("MovieApp HTTP server started on port 80");
33            } else {
34                LOGGER.info("MovieApp HTTP server failed to start");
35            }
36        });
37
38    }
39
40}

Vert.x انتظار یک وهله Verticle به عنوان نقطه ورودی اپلیکیشن دارد. بنابراین چنین نقطه‌ای را در کلاس Main ساختیم. این کلاس عملاً تنها به منظور io.vertx.core.AbstractVerticle عرضه شده است و متد چرخه عمری ()start کلاس را باطل می‌کند. درون ()start یک وهله از کلاس AppServer را ایجاد کرده و اجرا می‌کنیم. توجه کنید که یک آرگومان vertx به متد ()run مربوط به AppServer ارسال می‌کنیم. این یک وهله از io.vertx.core.Vertx که در AbstractVerticle تعریف شده است.

شاید کنجکاو باشید که Vert.x از کجا می‌داند که یابد از Main به عنوان نقطه ورودی برنامه استفاده کند. ما قبلاً آن را در فایل Vert.x به صورت مقدار properties: main.verticle تعریف کردیم.

کلاس AppServer اساساً کنترلر وب‌اپلیکیشن ما است. از نظر فنی این فقط یک کلاس قدیمی معمولی است که مستقیماً از java.lang.Object بسط یافته است. ما یک متد منفرد به نام ()run ایجاد کردیم که همه کارها را انجام می‌دهد.

در ()run یک وهله از io.vertx.core.http.HttpServer ساختیم که در پس‌زمینه از Jetty استفاده می‌کند. سپس یک وهله از io.vertx.ext.web.Router ایجاد می‌کنیم که درخواست‌های HTTP را به «دستگیره‌ها» (Handlers) مسیریابی می‌کند. دستگیره‌ها در واقع وهله‌هایی از کلاس <io.vertx.coreHandler<RoutingContext هستند که یک متد منفرد void handle(RoutingContext ctx) تعریف می‌کند، اما از لامبداهای جاوا 8 برای پیاده‌سازی آن دستگیره‌ها استفاده می‌کنیم. برای نمونه کد زیر دستگیره‌ای برای درخواست‌های GET در مسیر /movies تعریف می‌کند:

1router.get("/movies").handler()

در حالی که کد زیر دستگیره‌ای برای درخواست POST به همان مسیر تعریف می‌کند:

1router.post("/movies").handler()

دستگیره‌ها

دستگیره‌ها خودشان ساده هستند. آن‌ها صرفاً یک پاسخ متنی ساده به فراخواننده بازگشت می‌دهند. با این حال توجه کنید که ما در مورد بازگشت آن متن از یک متد صحبت نمی‌کنیم. از آنجا که Vert.x یک فریمورک واکنشی است که روی حلقه رویداد عمل می‌کند، باید صراحتاً بگوییم که چه هنگام آماده بازگرداندن پاسخ به فراخواننده هستیم.

بنابراین ابتدا متد ()handler یک وهله از RoutingContext می‌گیرد که در آن می‌تواند آن که باید استفاده کند را تشخیص دهد. سپس صرفاً متد ()end در HttpServerResponse فراخوانی می‌شود که پاسخ را به فراخواننده بازگشت می‌دهد.

در ادامه همه چیز را کنار هم قرار می‌دهیم و به HttpServer اعلام می‌کنیم که از Router به عنوان دستگیره درخواست استفاده کند و سپس راه‌اندازی شده و به پورت 8 گوش دهد. به این منظور یک لامبدا ارائه می‌کنیم که پس از تلاش سرور برای راه‌اندازی فراخوانی می‌شود و به ما اعلام می‌کند که آیا راه‌اندازی موفق بوده است یا نه. از این رویکرد callback به این جهت استفاده می‌کنیم که نیازی به مسدودسازی نخ Vert.x روی هیچ چیزی از جمله انتظار برای راه‌اندازی سرور نداریم.

در ادامه آن چه را که طراحی کرده‌ایم تست می‌کنیم. در خط فرمان در دایرکتوری /vertx پروژه، اقدام به کامپایل و بیلد کردن اپلیکیشن با اجرای دستور زیر می‌کنیم:

mvn clean install

زمانی که این کار انجام یافت یک دستور ls درون دایرکتوری اجرا می‌کنیم تا به دایرکتوری اعلام کنیم که آن چه را داریم بررسی کند. در اینجا باید متوجه یک زیردایرکتوری جدید به نام targe/t شویم. درون این زیردایرکتوری همه موارد مرتبط با build که تولید شده‌اند را می‌بینیم. مهم‌ترین مورد فایل movies-app-1.0.0-SNAPSHOT-fat.jar است. این فایل در واقع کل اپلیکیشن مستقل سمت سرور است که با یک وب‌سرور مبتنی بر Netty تکمیل می‌شود.

آن را در ادامه اجرا می‌کنیم. در یک دایرکتوری نمونه دستور زیر را اجرا کنید:

1java -jar target/movies-app-1.0.0-SNAPSHOT-fat.jar

بدین ترتیب باید چیزی مانند زیر ببینید:

1Movie app starting…
2Feb 27, 2020 7:23:37 AM io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer
3INFO: Succeeded in deploying verticle
4Feb 27, 2020 7:23:37 AM com.me.AppServer
5INFO: MovieApp HTTP server started on port 80

به مرورگر وب بروید و نشانی http://localhost/movies را وارد کنید که یک درخواست GET به پورت 80 می‌فرستد. بدین ترتیب با چیزی مانند تصویر زیر مواجه خواهید شد:

Vert.x

ذخیره وهله فیلم

به منظور ساده‌تر ماندن پروژه ما از ذخیره فیلم‌ها در یک پایگاه داده خودداری می‌کنیم و به جای آن فیلم‌ها را به صورت یک وهله در حافظه ذخیره می‌کنیم. این بدان معنی است که فیلم‌ها هر بار که سرور Vert.x ری‌استارت شود، ناپدید خواهند شد، اما با توجه به مقاصد آموزشی این پروژه مشکلی ندارد.

به یک مدل برای بازنمایی فیلم‌ها نیاز داریم. از این رو در دایرکتوری /me یک فایل به نام Movie.java اضافه می‌کنیم. مدل ما شامل فیلدهای زیر خواهد بود:

private String genre;
private String title;
private UUID guid;

بنابراین در نهایت کلاس ما به صورت زیر درمی‌آید:

1package com.me;
2
3import java.util.UUID;
4
5public class Movie {
6	
7    private String genre;
8    private String title;
9    private UUID guid;
10	
11    public Movie() {
12		
13    }
14	
15    public Movie(String genre, String title, UUID guid) {
16        super();
17        this.genre = genre;
18        this.title = title;
19        this.guid = guid;
20    }
21
22    public String getGenre() {
23        return genre;
24    }
25
26    public void setGenre(String genre) {
27        this.genre = genre;
28    }
29
30    public String getTitle() {
31        return title;
32    }
33
34    public void setTitle(String title) {
35        this.title = title;
36    }
37
38    public UUID getGuid() {
39        return guid;
40    }
41
42    public void setGuid(UUID guid) {
43        this.guid = guid;
44    }
45	
46}

اکنون کلاس AppServer را برای ذخیره فیلم‌ها آماده می‌کنیم. این متد را اضافه کنید:

1private void postMovie(RoutingContext ctx, Vertx vertx) {
2  final Movie movie = Json.decodeValue(
3      ctx.getBodyAsString(), Movie.class);
4  if (StringUtils.isEmpty(movie.getGenre()) ||
5      StringUtils.isEmpty(movie.getTitle())) {
6   ctx.response()
7   .setStatusCode(400)
8   .putHeader("content-type", "application/json")
9   .end("{ 'error': 'Title and Genre must by non-empty' }");
10 }
11 vertx.eventBus().request("service.movie-add", Json.encode(movie),
12   res -> {
13     if ( res.succeeded() ) {
14       ctx.response()
15       .putHeader("content-type", "application/json")
16       .end( res.result().body().toString() );
17     } else {
18       ctx.fail( res.cause() );
19     }
20   });
21}

Vertx این متد بدنه درخواست یعنی ()ctx.getBodyAsString را بازیابی می‌کند و JSON آن را به صورت یک فیلم دیکد خواهد کرد. این متد نوعی اعتبارسنجی ورودی مقدماتی اجرا کرده و یک وضعیت HTTP با کد 400 به همراه یک پیام خطای ساده JSON در صورت بروز شکست در اعتبارسنجی بازگشت می‌دهد.

با فرض این که اعتبارسنجی موفق باشد، در ادامه باس رویداد Vert.x به اپلیکیشن معرفی می‌شود. «باس رویداد» (Event Bus) روشی برای ارتباط کامپوننت‌های اپلیکیشن Vert.x با همدیگر در عین مستقل بودن از هم ارائه می‌کند. این کار از طریق انتشار و مصرف پیام صورت می‌گیرد. از لحاظ مفهومی می‌توانید باس رویداد را تقریباً مانند یک صف پیام تصور کنید. ارتباط باس رویداد در اپلیکیشن در فرایندی شبیه به تصویر زیر صورت می‌گیرد:

Vert.x

AppServer پیام‌هایی به دو کانال باس رویداد ارسال می‌کند: متد getMovie() به service.movie-get انتشار می‌دهد و متد ()postMovie نیز به کانال service.movie-add انتشار می‌دهد. یک verticle به نام MovieRepository که در ادامه ایجاد خواهیم کرد این پیام‌ها را مصرف کرده، آن‌ها را پردازش می‌کند و سپس پیام‌هایی در پاسخ به آن‌ها در همان کانال بازگشت می‌دهد.

از طریق وهله Vertx یک ارجاع به باس رویداد به دست می‌آوریم. سپس پیام را روی کانال service.movie-add همراه با وهله فیلم جدید به عنوان payload منتشر می‌کنیم. از آنجا که انتظار یک پیام در پاسخ به مصرف‌کننده پیام MovieRepository داریم، یک دستگیره پاسخ پیام نیز ارائه می‌کنیم. با فرض این که همه چیز به خوبی پیش برود، بدنه پیام را به صورت JSON به فراخواننده HTTP اصلی بازگشت می‌دهیم. اگر یک شکست رخ دهد، یک پاسخ شکست ژنریک (کد 500) بازگشت می‌دهیم.

برای استفاده از متد دستگیره درخواست ()postHandler باید دستگیره درخواست post خود را به صورت زیر ویرایش کنیم:

router.post("/movies").handler(ctx -> postMovie(ctx, vertx));

برای مدیریت صحیح درخواست‌های POST باید خط زیر را نیز به متد ()run درست زیر خطی که وهله router را می‌سازد، اضافه کنیم:

router.route().handler(BodyHandler.create());

اکنون verticle دوم را برای عرضه ریپازیتوری داده‌های خود ایجاد می‌کنیم. همچنان که پیش‌تر اشاره کردیم، ما فیلم‌ها را صرفاً در حافظه ذخیره می‌کنیم. اما این کلاس را می‌توان به عنوان یک ریپازیتوری نوعی یا شیء DAO تصور کرد که با پایگاه داده ارتباط می‌گیرد.

در دایرکتوری /me یک فایل به نام MovieRepository.java با محتوای زیر بسازید:

1package com.me;
2
3import java.util.ArrayList;
4import java.util.List;
5import java.util.UUID;
6
7import io.vertx.core.AbstractVerticle;
8import io.vertx.core.json.Json;
9
10public class MovieRepository extends AbstractVerticle {
11	
12	private static final List<Movie> MOVIES = new ArrayList<>();
13	
14	@Override
15	public void start() throws Exception {
16		System.out.println("MovieRepository starting...");
17		
18		vertx.eventBus().consumer("service.movie-add", msg -> {
19			Movie movie = Json.decodeValue((String)msg.body(), Movie.class);
20			movie.setGuid(UUID.randomUUID());
21			MOVIES.add(movie);
22			msg.reply(Json.encode(movie));
23		});
24
25		vertx.eventBus().consumer("service.movie-get", msg -> {
26			msg.reply(Json.encode(MOVIES));
27		});
28	}
29
30}

توجه داشته باشید که این کلاس نیز همانند کلاس Main اقدام به بسط AbstractVerticle می‌کند و متد ()start را باطل می‌سازد. در این جا ()start از وهله Vertx مربوط به verticle استفاده می‌کند تا دو مصرف‌کننده باس رویداد را بسازد. مورد نخست به کانال service.movie-add گوش می‌دهد و دستگیره‌ای برای دیکد پیام‌های رسیده از آن کانال به صورت وهله‌های Movie عرضه می‌کند. ضمناً یک UUID به فیلم‌ها انتساب می‌دهد و آن‌ها را در یک List درون حافظه نگهداری می‌کند. سپس یک پیام در پاسخ به باس رویداد منتشر می‌شود که شامل فیلم (به همراه UUID جدید) و بدنه آن است.

همچنان که احتمالاً حدس می‌زنید، مصرف‌کننده پیام‌های منتشر شده از سوی ()AppServer.postMovie را مدیریت می‌کند. مصرف‌کننده دوم در کانال service.movie-get مشترک می‌شود. دستگیره آن بی‌درنگ یک پیام در پاسخ منتشر می‌کند که شامل لیست کامل فیلم‌ها در بدنه است. در ادامه کلاس AppServer از آن مصرف‌کننده دوم استفاده می‌کند. بنابراین متد زیر را اضافه کنید:

1private void getMovie(RoutingContext ctx, Vertx vertx) {
2  vertx.eventBus().request("service.movie-get", "", res -> {
3    if ( res.succeeded() ) {
4      ctx.response()
5      .putHeader("content-type", "application/json")
6      .end( res.result().body().toString() );
7    } else {
8      ctx.fail( res.cause() );
9    }
10  });
11}

بدین ترتیب به عنوان یک دستگیره درخواست HTTP برای درخواست‌های GET عمل می‌کند. سپس بی‌درنگ یک پیام به کانال service.movie-get باس رویداد ارسال می‌شود. بدنه پیام مهم نیست و از این رو یک رشته خالی ارسال خواهد شد. همچنین یک دستگیره برای بازگشت پیام ارائه می‌شود که بدنه پیام را به صورت یک payload جیسون به فراخواننده اصلی HTTP بازمی‌گرداند. در صورت بروز خطا نیز کد 500 بازمی‌گردد.

البته در MovieRepository قبلاً یک مصرف‌کننده کانال service.movie-get اضافه کرده‌ایم که لیستی از فیلم‌های قبلاً ایجاد شده را بازیابی می‌کند. برای بهره‌برداری از دستگیره ()getMovie دستگیره درخواست get را به صورت زیر ویرایش می‌کنیم:

1router.get("/movies").handler(ctx -> getMovie(ctx, vertx));

شکل نهایی AppServer.java باید به صورت زیر باشد:

1package com.me;
2
3import org.apache.commons.lang3.StringUtils;
4
5import io.vertx.core.Vertx;
6import io.vertx.core.http.HttpServer;
7import io.vertx.core.json.Json;
8import io.vertx.core.logging.Logger;
9import io.vertx.core.logging.LoggerFactory;
10import io.vertx.ext.web.Router;
11import io.vertx.ext.web.RoutingContext;
12import io.vertx.ext.web.handler.BodyHandler;
13import io.vertx.ext.web.handler.StaticHandler;
14
15public class AppServer {
16
17    private final Logger LOGGER = LoggerFactory.getLogger( AppServer.class );
18
19    public void run(Vertx vertx) {
20        HttpServer server = vertx.createHttpServer();
21
22        Router router = Router.router(vertx);
23        router.route().handler(BodyHandler.create());
24
25		router.get().handler(StaticHandler.create());
26
27        router.get("/movies").handler(ctx -> getMovie(ctx, vertx));
28        
29        router.post("/movies").handler(ctx -> postMovie(ctx, vertx));
30
31        server.requestHandler(router).listen(80, http -> {
32            if (http.succeeded()) {
33                LOGGER.info("MovieApp HTTP server started on port 80");
34            } else {
35                LOGGER.info("MovieApp HTTP server failed to start");
36            }
37        });
38    }
39    
40    private void getMovie(RoutingContext ctx, Vertx vertx) {
41    	vertx.eventBus().request("service.movie-get", "", res -> {
42    		if ( res.succeeded() ) {
43    			ctx.response()
44    			.putHeader("content-type", "application/json")
45    			.end( res.result().body().toString() );
46    		} else {
47    			ctx.fail( res.cause() );
48    		}
49    	});    	
50    }
51    
52    private void postMovie(RoutingContext ctx, Vertx vertx) {
53    	final Movie movie = Json.decodeValue(ctx.getBodyAsString(), Movie.class);
54    	if (StringUtils.isEmpty(movie.getGenre()) || StringUtils.isEmpty(movie.getTitle())) {
55    		ctx.response()
56    		.setStatusCode(400)
57			.putHeader("content-type", "application/json")
58			.end("{ 'error': 'Title and Genre must by non-empty' }");
59    	}
60    	vertx.eventBus().request("service.movie-add", Json.encode(movie), res -> {
61    		if ( res.succeeded() ) {
62    			ctx.response()
63    			.putHeader("content-type", "application/json")
64    			.end( res.result().body().toString() );
65    		} else {
66    			ctx.fail( res.cause() );
67    		}
68    	});
69    }
70
71}

پیش از آن که از این تغییرات عملاً استفاده کنیم، یک قطعه دیگر نیز به کلاس Main اضافه می‌کنیم. MovieRepository یک verticle است و هیچ جا وهله‌سازی نشده است. این بدان معنی است که فعلاً مشترکان باس رویداد آن هرگز ایجاد نخواهند شد و از این رو فیلم‌ها ذخیره یا بازیابی نمی‌شوند. ورتیکل های Vert.x باید توزیع شوند و این کار را در Main انجام می‌دهیم. ابتدا این متد را اضافه می‌کنیم:

1protected void deployVerticle(String className) {
2  vertx.deployVerticle(className, res -> {
3    if (res.succeeded()) {
4      System.out.printf("Deployed %s verticle \n", className);
5    } else {
6      System.out.printf("Error deploying %s verticle:%s \n", className, res.cause());
7    }
8  });
9}

این متد را به صورت یک متد ژنریک برای توزیع هر ورتیکل طراحی کرده‌ایم. از وهله vertx موجود در Main برای توزیع verticle استفاده می‌کنیم و یک دستگیره callback ارائه می‌کنیم که نتیجه را لاگ می‌کند. یک دستگیره callback الزامی است، زیرا نخ جاری را در زمانی که verticle توزیع شده است مسدود نمی‌سازیم.

سپس کد زیر را به متد ()start اضافه می‌کنیم:

1deployVerticle(MovieRepository.class.getName());

بار دیگر اپلیکیشن را کامپایل و اجرا می‌کنیم:

mvn clean install
java -jar target/movies-app-1.0.0-SNAPSHOT-fat.jar

با مراجعه به نشانی localhost/movies در مرورگر وب باید یک استایل خالی بازگشت یابد، چون هنوز فیلمی اضافه نکرده‌ایم. باید از یک ابزار مانند Postman برای ایجاد درخواست POST به نشانی localhost/movies استفاده کنید. بدنه درخواست را به صورت زیر ارائه کنید:

1{
2  "title": "Dances With Wolves",
3  "genre": "drama"
4}

اگر همه چیز به خوبی پیش برود، یک پاسخ دریافت می‌کنیم که شامل فیلم جدیداً ذخیره شده همراه با مقدار guid جدید آن است:

1{
2  "genre": "drama",
3  "title": "Dances With Wolves",
4  "guid": "7254537a-82d2-4bee-96d3-f12c61bc2cd9"
5}

Vert.x

اکنون به مرورگر وب بازمی‌گردیم و درخواست GET دیگری ارسال می‌کنیم:

Vert.x

اینک در آخرین گام اپلیکیشن‌های React و Vert.x را به هم متصل می‌کنیم.

واکشی و نمایش فیلم‌ها

به کد ری‌اکت بازمی‌گردیم و فایل MovieList.js را ویرایش می‌کنیم به طوری که موارد زیر را داشته باشد:

کار خود را با افزودن کد زیر به ابتدای فایل درست زیر ایمپورت‌ها آغاز می‌کنیم:

1var xhr;

سپس کد زیر را با سازنده MovieList اضافه می‌کنیم:

1this.state = {
2 movies: []
3}
4this.sendRequest = this.sendRequest.bind(this);
5this.processRequest = this.processRequest.bind(this);
6this.props.eventDispatcher.subscribe(“addMovie”, this.sendRequest);

در کد فوق یک شیء state ایجاد می‌کنیم که شامل یک آرایه خالی به نام movies است. پس از اتصال دو تابع جدید که در ادامه ایجاد خواهیم کرد، در یک dispatcher رویداد مشترک می‌شویم (که آن را نیز در ادامه کدنویسی خواهیم کرد). بدین ترتیب MovieList می‌تواند هر زمان که یک فیلم جدید ایجاد می‌شود مطلع شود.

این تابع sendRequest را اضافه می‌کنیم:

1sendRequest() {
2  xhr = new XMLHttpRequest();
3  xhr.open("GET", "http://localhost/movies")
4  xhr.send();
5  xhr.addEventListener("readystatechange", this.processRequest, false);
6 }

این تابع یک وهله جدید از XMLHttpRequest به متغیر xhr انتساب می‌دهد تا به آن اعلام کند که باید یک درخواست GET به نشانی localhost/movies ارسال کند. ما به منظور سادگی و وضوح مطالب URL-ها را هاردکد کرده‌ایم، اما این کار در محیط پروداکشن جایز نیست. همچنین تابع processRequest را به صورت یک callback برای درخواست‌های موفق شناسایی می‌کنیم.

در ادامه تابع processRequest را ایجاد می‌کنیم:

1processRequest() {
2  if (xhr.readyState === 4 && xhr.status === 200) {
3    var response = JSON.parse(xhr.responseText);
4    this.setState({
5      movies: response
6    })
7  }
8}

این متد صرفاً محتوای بدنه پاسخ‌های موفق را به درخواست‌های GET تحلیل می‌کند. به خاطر داشته باشید که نقطه انتهایی GET در Vert.x یک لیست از اشیای فیلم بازگشت می‌دهند. بنابراین کافی است حالت MovieList را روی محتوا تنظیم کنیم. اگر به خاطر داشته باشید، این کار موجب می‌شود که کامپوننت MovieList در UI مجدداً رندر شود.

ما می‌خواهیم مطمئن شویم که لیستی از فیلم‌ها را در زمانی که UI نخستین بار بارگذاری می‌شود، بازیابی می‌کنیم. به این جهت از قلاب چرخه عمری ()componentDidMount بهره می‌گیریم. این متدی است که در شیء کامپوننت ری‌اکت تعریف شده است و می‌توانیم آن را override کنیم. همچنان که ممکن است حدس بزنید، این متد زمانی فراخوانی می‌شود که کامپوننت به طور کامل بارگذاری شده باشد.

1componentDidMount() {
2  this.sendRequest()
3}

در نهایت تغییر عمده‌ای درون متد ()render ایجاد می‌کنیم. فیلم‌هایی که نمایش می‌یابند اینک باید از حالت MovieList بیایند، بنابراین اکنون ردیف‌های فیلم را از طریق کد زیر ایجاد می‌کنیم:

1{this.state.movies.map(this.toMovie)}

حفظ فیلم‌ها

در این بخش فایل MovieForm.js را طوری بهبود می‌بخشیم که در عمل فیلم‌ها را در اپلیکیشن Vert.x نگهداری کند. همانند MovieList.js کد زیر را به ابتدای فایل اضافه می‌کنیم:

1var xhr;

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

1this.tryCreateMovie = this.tryCreateMovie.bind(this);
2this.processRequest = this.processRequest.bind(this);

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

1tryCreateMovie() {
2  xhr = new XMLHttpRequest();
3  xhr.open("POST", "http://localhost/movies")
4  xhr.send(JSON.stringify({ “title”: this.state.title, "genre": this.state.genre }));
5  xhr.addEventListener("readystatechange", this.processRequest, false);
6 }

این تابع یک شیء جدید XMLHttpRequest به متغیر xhr انتساب می‌دهد و سپس یک شکل Json-شده از فیلم را که ایجاد کردیم به http://localhost/movies ارسال می‌کند. در پاسخ‌های موفق، تابع callback به نام processRequest فراخوانی می‌شود که به صورت زیر است:

1processRequest() {
2  if (xhr.readyState === 4 && xhr.status === 200) {
3    this.props.eventDispatcher.dispatch("addMovie", "")
4    this.changeState( { title: ""} )
5  }
6}

این تابع یک پیام addMovie به دیسپچر رویداد انتشار می‌دهد. همچنین حالت MovieForm را به یک فیلد با عنوان خالی تغییر می‌دهیم تا فیلد متنی Title را پاک کنیم.

اینک زمان آن رسیده است که کد dispatcher رویداد خود را بنویسیم. این کار را در فایل App.js انجام خواهیم داد. ابتدا باید از شر آرایه movies رها شویم، چون دیگر به آن نیازی نداریم. در این محل یک شیء ساده مانند زیر ایجاد می‌کنیم:

1const eventDispatcher = {
2  listeners: {},
3  dispatch: function(event, data) {
4    if (this.listeners[event]) {
5      this.listeners[event].forEach(function(l) {
6        l(data);
7      });
8    }
9  },
10  subscribe: function(event, f) {
11    if (!this.listeners[event]) this.listeners[event] = [];
12    this.listeners[event].push(f)
13  }
14}

البته فیلد listeners به صورت یک شیء خالی اعلان شده است. در نهایت به صورت نوعی map از لیست‌ها عمل می‌کند. کلیدها به صورت رشته متنی هستند و انواع رویداد را نمایش می‌دهند، در حالی که مقادیر آرایه‌ای از تابع‌های callback هستند که هر زمان رویدادی منتشر شود فراخوانی خواهند شد.

تابع subscribe یک نام رویداد و یک تابع callback می‌گیرد. کار آن این است که تابع callback را به نام رویداد اضافه می‌کند. تابع dispatch یک نام رویداد (رشته متنی) و داده‌ها را می‌گیرد. سپس بررسی می‌کند آیا کلید شیء listeners با نام رویداد تطبیق دارد یا نه. اگر کلید تطبیق یافت، روی تابع‌های callback ذخیره شده در آرایه مرتبط می‌چرخد و آن‌ها را فرامی‌خواند و داده‌ها را ارسال می‌کند.

دیسپچر رویداد ممکن است در این اپلیکیشن کوچک اقدامی غیرضروری به نظر برسد، اما در صورتی که بخواهیم اپلیکیشن را گسترش دهیم، موجب ساده‌تر شدن مدیریت تغییر حالت در کل اپلیکیشن می‌شود. به علاوه کاملاً ژنریک است و می‌توان در اپلیکیشن‌های ری‌اکت دیگر نیز از آن بهره گرفت. برای مدیریت حالت در چنین اپلیکیشن‌های پیچیده‌ای معمولاً از Redux استفاده می‌شود. ما در این مقاله موضوع ریداکس را بررسی نمی‌کنیم، اما شما می‌توانید در ادامه و به عنوان یک تمرین آن را در اپلیکیشن پیاده‌سازی کنید.

در نهایت ایجاد کامپوننت‌های MovieForm و MovieList را درون تابع ()App ویرایش می‌کنیم. ارائه movies را حذف کرده‌ایم و از این رو نمی‌توانیم MovieList را دیگر ارسال کنیم. هر دو مورد MovieForm و MovieList به یک ارجاع برای دیسپچر رویداد نیاز دارند. از این رو اعلان‌های کامپوننت را به صورت زیر تغییر می‌دهیم:

1 <MovieForm genres={genres} eventDispatcher={eventDispatcher} />
2 <MovieList genres={genres} eventDispatcher={eventDispatcher} />

جمع‌بندی

در این بخش یک شل اسکریپت کوچک برای پکیج کردن کل اپلیکیشن می‌سازیم. هر دو بخش React و Java/Vert.x در یک فایل اجرایی ترکیب می‌شوند. در دایرکتوری react-app/ یک اسکریپت به نام deploy.sh ایجاد کنید که دارای محتوای زیر باشد:

npm run build
if [-d "../vertx/src/main/resources/webroot/"]; then rm -Rf../vertx/src/main/resources/webroot/; fi
mkdir../vertx/src/main/resources/webroot/
cp -R build/*../vertx/src/main/resources/webroot/

در این اسکریپت از npm برای بیلد اپلیکیشن و بهینه‌سازی برای یک توزیع پروداکشن استفاده می‌کنیم. باید مطمئن شویم که زیردایرکتوری خالی src/main/resources/webroot در دایرکتوری vertx وجود دارد. در نهایت اپلیکیشن ری‌اکت ساخته شده را به دایرکتوری src/main/resources/webroot منتقل می‌کنیم تا در آنجا به صورت محتوای استاتیک از اپلیکیشن Vert.x عرضه شود.

با اجرای دستور زیر مطمئن شوید که اسکریپت قابلیت اجرایی دارد:

chmod 777 deploy.sh

سپس دستور زیر را اجرا کنید:

./deploy.sh

سپس به دایرکتوری vertx بروید و اپلیکیشن را با دستور زیر بیلد و اجرا کنید:

mvn clean install

java -jar target/movies-app-1.0.0-SNAPSHOT-fat.jar

در نهایت به نشانی http://localhost در مرورگر بروید تا نتیجه را مشاهده کنید:

Vert.x

نتیجه باید برای شما آشنا باشد، اکنون چند فیلم اضافه می‌کنیم:

Vert.x

با رفرش کردن مرورگر، فیلم‌ها دوباره واکشی شده و دوباره رندر می‌شوند.

سخن پایانی

در صورتی که در اجرای اپلیکیشن‌ها با هر نوع مشکلی مواجه شدید می‌توانید کد خودتان را با کد موجود در این ریپوی گیت‌هاب (+) تطبیق بدهید. در این مقاله یک بازنمایی ساده از روش ساخت وب‌اپلیکیشن‌ها با استفاده از ری‌اکت و Vert.x ارائه کردیم. برای بهبود این اپلیکیشن می‌توانید ایده‌های زیر را به آن اضافه کنید:

  • افزودن فیلدهای بیشتر برای مدل فیلم.
  • ذخیره فیلم‌ها به صورت دائمی در یک دیتا استور مانند یک پایگاه داده MongoDB یا MySQL لوکال یا ذخیره ابری داده‌ها.
  • استفاده از Redux برای انتشار حالت اپلیکیشن ری‌اکت.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
better-programming
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *