مرحبا بالجميع!
في العام الماضي ، تم إطلاق دورة
Java Enterprise Developer بنجاح ، ولدينا آخر مادة حول هذا الموضوع نود مشاركتها معك ، والتي تناقش استخدام النهج غير المتزامن والتدريج لتطوير التطبيقات سريعة الاستجابة.
دعنا نذهب.
تبدو البرمجة التفاعلية في البداية مثل اسم نموذج ناشئ ، لكنها في الواقع تشير إلى طريقة برمجة يتم فيها استخدام نهج موجه نحو الحدث للعمل مع تدفقات البيانات غير المتزامنة. بناءً على البيانات الحالية باستمرار ، تستجيب الأنظمة التفاعلية لها من خلال إجراء سلسلة من الأحداث.
تتبع البرمجة التفاعلية نمط التصميم “Observer” ، والذي يمكن تعريفه على النحو التالي: إذا تغيرت الحالة في كائن واحد ، فسيتم إعلام جميع الكائنات الأخرى وتحديثها وفقًا لذلك. لذلك ، بدلاً من استطلاع الأحداث للتغييرات ، يتم دفع الأحداث بشكل غير متزامن حتى يتمكن المراقبون من معالجتها. في هذا المثال ، المراقبون هم وظائف يتم تنفيذها عند إرسال الحدث. وتدفق البيانات المذكورة هو الملاحظة الفعلية.
تستخدم جميع اللغات والأطر تقريبًا هذا النهج في نظامها البيئي ، ولا تعد أحدث إصدارات جافا استثناءً. في هذه المقالة ، سأشرح كيف يمكن تطبيق البرمجة التفاعلية باستخدام أحدث إصدار من JAX-RS في وظائف Java EE 8 و Java 8.
بيان جتيسرد
جت مانيفستو أربعة جوانب أساسية يحتاج التطبيق إلى أن يكون أكثر مرونة ، مقرونًا بشكل فضفاض ، وسهل القياس ، وبالتالي قادر على أن يكون تفاعليًا. وينص على أن التطبيق يجب أن يكون متجاوبًا ومرنًا (وبالتالي قابلًا للتطوير) ومرنًا ومدفوعًا بالرسائل.
الهدف الأساسي هو تطبيق سريع الاستجابة. افترض أن هناك تطبيقًا يشارك فيه مؤشر ترابط كبير في معالجة طلبات المستخدم ، وبعد الانتهاء من العمل ، يرسل هذا الموضوع ردودًا إلى مقدمي الطلبات الأصليين. عندما يتلقى التطبيق طلبات أكثر مما يستطيع التعامل معها ، يصبح هذا الخيط اختناق ، ويفقد التطبيق استجابته السابقة. للحفاظ على الاستجابة ، يجب أن يكون التطبيق قابلاً للتطوير ومرنًا. يمكن اعتباره مستدامًا تطبيقًا يتميز بوظيفة الاسترداد التلقائي. في تجربة معظم المطورين ، فإن البنية المبنية على الرسائل فقط هي التي تسمح للتطبيق بأن يكون قابلاً للتطوير والمرونة والاستجابة.
تم تقديم البرمجة التفاعلية في Java 8 و Java EE 8. قدمت Java مفاهيم مثل
CompletionStage
وتطبيقها لـ
CompletableFuture
، وبدأت Java في استخدام هذه الميزات في المواصفات مثل واجهة برمجة تطبيقات Reactive Client في JAX-RS.
واجهة برمجة تطبيقات عميل تفاعلي JAX-RS 2.1دعونا نرى كيف يمكن استخدام البرمجة التفاعلية في تطبيقات Java EE 8. لفهم العملية ، تحتاج إلى بعض المعرفة الأساسية بواجهة برمجة تطبيقات Java EE API.
قدم JAX-RS 2.1 طريقة جديدة لإنشاء عميل REST مع دعم البرمجة التفاعلية. تطبيق Invoker الافتراضي المعروض في JAX-RS متزامن ، مما يعني أن العميل الذي تم إنشاؤه سيرسل مكالمة حظر إلى نقطة نهاية الخادم. يتم تقديم مثال على التنفيذ في القائمة 1.
الإدراج 1
Response response = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .get();
بدءًا من الإصدار 2.0 ، يوفر JAX-RS الدعم لإنشاء Invoker غير المتزامن على واجهة برمجة تطبيقات العميل بمكالمة بسيطة لأسلوب
async()
، كما هو موضح في القائمة 2.
قائمة 2
Future<Response> response = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .async() .get();
يؤدي استخدام invoker غير متزامن على العميل إلى إرجاع نسخة
Future
من النوع
javax.ws.rs.core.Response
. يمكن أن يؤدي ذلك إلى استطلاع الرد ، من خلال مكالمة إلى
future.get()
، أو تسجيل رد اتصال سيتم استدعاؤه عند توفر استجابة HTTP. كلا التطبيقين مناسبان للبرمجة غير المتزامنة ، ولكن الأمور عادة ما تكون معقدة إذا كنت ترغب في تجميع عمليات رد الاتصال أو إضافة حالات شرطية إلى هذه الحدود الدنيا للتنفيذ غير المتزامن.
يوفر JAX-RS 2.1 طريقة تفاعلية للتغلب على هذه المشاكل باستخدام واجهة برمجة تطبيقات JAX-RS Reactive Client الجديدة لبناء عميل. الأمر بسيط مثل استدعاء طريقة
rx()
أثناء بناء العميل. في القائمة 3 ، تُرجع طريقة
rx()
المستدعي التفاعلي الموجود في وقت تشغيل العميل ، ويعيد العميل استجابة من النوع
CompletionStage.rx()
، والذي يسمح بالانتقال من المستثمر المتزامن إلى المستثمر غير المتزامن مع مكالمة بسيطة.
قائمة 3
CompletionStage<Response> response = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .rx() .get();
CompletionStage<>
هي واجهة جديدة تم تقديمها في Java 8. وهي تمثل عملية حسابية ، والتي يمكن أن تكون خطوة في إطار عملية حسابية أكبر ، كما يوحي الاسم. هذا هو ممثل تفاعلية Java 8 الوحيد الذي يصل إلى JAX-RS.
بعد تلقي مثيل الاستجابة ، يمكنني استدعاء
AcceptAsync()
، حيث يمكنني تقديم جزء من التعليمات البرمجية سيتم تنفيذه بشكل غير متزامن عندما تصبح الاستجابة متاحة ، كما هو موضح في القائمة 4.
قائمة 4
response.thenAcceptAsync(res -> { Temperature t = res.readEntity(Temperature.class);
إضافة تفاعل إلى نقطة نهاية RESTلا يقتصر النهج التفاعلي على جانب العميل في JAX-RS ؛ يمكن استخدامه أيضًا على جانب الخادم. على سبيل المثال ، سأقوم أولاً بإنشاء برنامج نصي بسيط حيث يمكنني طلب قائمة بمواقع وجهة واحدة. لكل موقف ، سوف أقوم بإجراء مكالمة منفصلة مع بيانات الموقع إلى نقطة أخرى للحصول على قيم درجة الحرارة. سيكون تفاعل الوجهات كما هو موضح في الشكل 1.
الشكل 1. التفاعل بين نقاط الوجهةأولاً ، أحدد ببساطة نموذج المجال ، ثم الخدمات لكل نموذج. تعرض القائمة 5 كيفية تعريف فئة
Forecast
، والتي تلتف فئتي
Location
ودرجة
Temperature
.
قائمة 5
public class Temperature { private Double temperature; private String scale;
ServiceResponse
قائمة التوقعات ،
ServiceResponse
تنفيذ فئة
ServiceResponse
في القائمة 6.
قائمة 6
public class ServiceResponse { private long processingTime; private List<Forecast> forecasts = new ArrayList<>(); public void setProcessingTime(long processingTime) { this.processingTime = processingTime; } public ServiceResponse forecasts(List<Forecast> forecasts) { this.forecasts = forecasts; return this; }
LocationResource
الموضح في القائمة 7 ثلاثة مواقع عينة تم إرجاعها مع مسار
/location
.
قائمة 7
@Path("/location") public class LocationResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response getLocations() { List<Location> locations = new ArrayList<>(); locations.add(new Location("London")); locations.add(new Location("Istanbul")); locations.add(new Location("Prague")); return Response.ok(new GenericEntity<List<Location>>(locations){}).build(); } }
إرجاع مصدر
TemperatureResource
الموضح في القائمة 8 قيمة درجة حرارة يتم إنشاؤها عشوائيًا بين 30 و 50 لموقع معين. تمت إضافة تأخير 500 مللي ثانية إلى التطبيق لمحاكاة قراءة المستشعر.
قائمة 8
@Path("/temperature") public class TemperatureResource { @GET @Path("/{city}") @Produces(MediaType.APPLICATION_JSON) public Response getAverageTemperature(@PathParam("city") String cityName) { Temperature temperature = new Temperature(); temperature.setTemperature((double) (new Random().nextInt(20) + 30)); temperature.setScale("Celsius"); try { Thread.sleep(500); } catch (InterruptedException ignored) { ignored.printStackTrace(); } return Response.ok(temperature).build(); } }
أولاً ، سوف أعرض تطبيق
ForecastResource
المتزامنة (انظر القائمة 9) ، التي تُرجع جميع المواقع. ثم ، بالنسبة لكل موقف ، يتصل بخدمة درجة الحرارة للحصول على القيم بالدرجات المئوية.
قائمة 9
@Path("/forecast") public class ForecastResource { @Uri("location") private WebTarget locationTarget; @Uri("temperature/{city}") private WebTarget temperatureTarget; @GET @Produces(MediaType.APPLICATION_JSON) public Response getLocationsWithTemperature() { long startTime = System.currentTimeMillis(); ServiceResponse response = new ServiceResponse(); List<Location> locations = locationTarget .request() .get(new GenericType<List<Location>>(){}); locations.forEach(location -> { Temperature temperature = temperatureTarget .resolveTemplate("city", location.getName()) .request() .get(Temperature.class); response.getForecasts().add( new Forecast(location).setTemperature(temperature)); }); long endTime = System.currentTimeMillis(); response.setProcessingTime(endTime - startTime); return Response.ok(response).build(); } }
عند طلب وجهة التوقعات على أنها
/forecast
، ستحصل على ناتج مشابه لما هو موضح في القائمة 10. لاحظ أن وقت معالجة الطلب استغرق 1.533 مللي ثانية ، وهو أمر منطقي ، نظرًا لأن الطلب المتزامن لقيم درجة الحرارة من ثلاثة مواقع مختلفة يضيف ما يصل إلى 1.5 مللي ثانية
قائمة 10
{ "forecasts": [ { "location": { "name": "London" }, "temperature": { "scale": "Celsius", "temperature": 33 } }, { "location": { "name": "Istanbul" }, "temperature": { "scale": "Celsius", "temperature": 38 } }, { "location": { "name": "Prague" }, "temperature": { "scale": "Celsius", "temperature": 46 } } ], "processingTime": 1533 }
حتى الآن ، كل شيء يسير حسب الخطة. حان الوقت لإدخال البرمجة التفاعلية على جانب الخادم ، حيث يمكن إجراء مكالمات لكل موقع بالتوازي بعد تلقي جميع المواقع. يمكن أن يؤدي ذلك إلى تحسين الدفق المتزامن الذي تم عرضه مسبقًا. يتم ذلك في القائمة 11 ، التي توضح تعريف إصدار خدمة التنبؤات التفاعلية.
قائمة 11
@Path("/reactiveForecast") public class ForecastReactiveResource { @Uri("location") private WebTarget locationTarget; @Uri("temperature/{city}") private WebTarget temperatureTarget; @GET @Produces(MediaType.APPLICATION_JSON) public void getLocationsWithTemperature(@Suspended final AsyncResponse async) { long startTime = System.currentTimeMillis();
قد يبدو التنفيذ التفاعلي معقدًا للوهلة الأولى ، ولكن بعد دراسة أكثر دقة ، ستلاحظ أنه بسيط للغاية. في تطبيق
ForecastReactiveResource
أقوم أولاً بإنشاء اتصال عميل لخدمات الموقع باستخدام واجهة برمجة تطبيقات عميل تفاعلية JAX-RS. كما ذكرت أعلاه ، هذه إضافة لـ Java EE 8 ، وتساعد على إنشاء مكالمة تفاعلية ببساطة باستخدام طريقة
rx()
.
أقوم الآن بإنشاء مرحلة جديدة استنادًا إلى الموقع لوضع قائمة بالتنبؤات. سيتم تخزينها كقائمة توقعات في مرحلة واحدة كبيرة من الإنجاز تسمى
forecastCS
. في النهاية ، سأقوم بإنشاء استجابة لمكالمة الخدمة باستخدام
forecastCS
خدمة العملاء فقط.
والآن ، دعونا نجمع التوقعات في شكل قائمة بمراحل الإنجاز المحددة في قائمة
forecastList
المتغيرات. لإنشاء مرحلة إكمال لكل توقعات ، أمرر البيانات حسب الموقع ، ثم أنشئ متغير
tempCS
، مرة أخرى باستخدام JAX-RS Reactive Client API ، الذي يستدعي خدمة درجة الحرارة باسم المدينة. هنا ، أستخدم طريقة
resolveTemplate()
لبناء العميل ، وهذا يسمح لي بتمرير اسم المدينة إلى المجمع كمعلمة.
كخطوة أخيرة في البث ، أقوم بإجراء مكالمة إلى
CompletableFuture.completedFuture()
، لتمرير مثيل
Forecast
الجديد كمعلمة. أقوم بدمج هذا المستقبل مع مرحلة
tempCS
حتى أحصل على قيمة درجة حرارة للمواقع المراقبة.
تحول طريقة
CompletableFuture.allOf()
في القائمة 11 قائمة مرحلة الإكمال إلى
forecastCS
. يؤدي تنفيذ هذه الخطوة إلى إرجاع نسخة مستقبلية كبيرة قابلة للإكمال عند اكتمال جميع الكائنات المستقبلية القابلة للإكمال.
تعد استجابة الخدمة
ServiceResponse
لفئة
ServiceResponse
، لذلك أقوم بإنشاء مستقبل مكتمل ، ثم أقوم بدمج مرحلة إكمال خدمة
forecastCS
ServiceResponse
مع قائمة من التوقعات وحساب وقت استجابة الخدمة.
بالطبع ، تفرض البرمجة التفاعلية جانب الخادم فقط للعمل بشكل غير متزامن ؛ سيتم حظر جانب العميل حتى يرسل الخادم استجابة إلى الطالب. للتغلب على هذه المشكلة ، يمكن استخدام الأحداث المرسلة من الخادم (SSEs) لإرسال استجابة جزئيًا بمجرد توفرها بحيث يتم نقل قيم درجة الحرارة لكل موقع إلى العميل واحدًا تلو الآخر. سيكون ناتج
ForecastReactiveResource
مشابهًا لما هو معروض في القائمة 12. كما هو موضح في الإخراج ، فإن وقت المعالجة هو 515 مللي ثانية ، وهو وقت تشغيل مثالي للحصول على قيم درجة الحرارة من موقع واحد.
قائمة 12
{ "forecasts": [ { "location": { "name": "London" }, "temperature": { "scale": "Celsius", "temperature": 49 } }, { "location": { "name": "Istanbul" }, "temperature": { "scale": "Celsius", "temperature": 32 } }, { "location": { "name": "Prague" }, "temperature": { "scale": "Celsius", "temperature": 45 } } ], "processingTime": 515 }
الخلاصةفي الأمثلة الواردة في هذه المقالة ، عرضت أولاً طريقة متزامنة للحصول على توقعات باستخدام خدمات الموقع ودرجة الحرارة. ثم ، انتقلت إلى النهج التفاعلي بحيث تتم المعالجة غير المتزامنة بين مكالمات الخدمة. عند استخدام JAX-RS Reactive Client API في Java EE 8 ، جنبًا إلى جنب مع فئات
CompletionStage
و
CompletableFuture
المتوفرة في Java 8 ، فإن قوة المعالجة غير المتزامنة تتعطل بفضل البرمجة التفاعلية.
البرمجة التفاعلية أكثر من مجرد تطبيق نموذج غير متزامن من نموذج متزامن ؛ كما يبسط العمل مع مفاهيم مثل مرحلة التعشيش. كلما زاد استخدامه ، كان من الأسهل إدارة البرامج النصية المعقدة في البرمجة المتوازية.
النهاية
شكرا لكم على اهتمامكم. كما هو الحال دائمًا ، نحن في انتظار تعليقاتك وأسئلتك.