Quarkus: ترقيات التطبيق باستخدام مثال helloworld من JBoss EAP Quickstart

مرحباً بالجميع على هذه المدونة ، ومعكم المنشور الرابع من سلسلة Quarkus! (بالمناسبة ، قم بالتسجيل وانتقل إلى ندوتنا على الويب " هذا هو Quarkus - Kubernetes الأصلي Java Framework Framework " ، الذي سيعقد في 27 مايو . سنوضح كيفية البدء من الصفر أو نقل الحلول الجاهزة) كان المنشور



السابق حول كيفية الجمع بين Quarkus و MicroProfile و Spring. تذكر أن Quarkus تم وضعه على أنه "Java فائق السرعة دون ذري" ، وهو "مكدس Java الموجه Kubernetes ، شحذ بواسطة GraalVM و OpenJDK HotSpot وتم تجميعه من أفضل المكتبات والمعايير." نعرض اليوم كيفية ترقية تطبيقات Java الحالية باستخدام إمكانات Quarkus باستخدام تطبيق helloworld من مستودع تطبيقات Red Hat JBoss Enterprise Platform (JBoss EAP) Quickstartباستخدام تقنيات CDI و Servlet 3 المدعومة من Quarkus.

من المهم أن نلاحظ هنا أن كل من Quarkus و JBoss EAP يركزان على استخدام الأدوات التي تم إنشاؤها وفقًا لأقصى المعايير. أليس لديك تطبيق يعمل على JBoss EAP؟ ليست مشكلة ، يمكن ترحيلها بسهولة من خادم التطبيق الحالي إلى JBoss EAP باستخدام مجموعة أدوات تطبيق Red Hat . بعد ذلك ، ستتوفر النسخة النهائية والعملية من الشفرة التي تمت ترقيتها في github.com/mrizzi/jboss-eap-quickstarts/tree/quarkus مستودع ، في وحدة helloworld .

تمت كتابة هذا المنشور باستخدام دروس Quarkus التعليمية ، بشكل أساسي إنشاء تطبيقك الأول وبناء ملف تنفيذي أصلي .

نحصل على رمز


أولاً ، أنشئ نسخة محلية من مستودع JBoss EAP quickstarts :

$ git clone https://github.com/jboss-developer/jboss-eap-quickstarts.git
Cloning into 'jboss-eap-quickstarts'...
remote: Enumerating objects: 148133, done.
remote: Total 148133 (delta 0), reused 0 (delta 0), pack-reused 148133
Receiving objects: 100% (148133/148133), 59.90 MiB | 7.62 MiB/s, done.
Resolving deltas: 100% (66476/66476), done.
$ cd jboss-eap-quickstarts/helloworld/

شاهد كيف يعمل helloworld الأصلي


في الواقع ، جوهر هذا التطبيق واضح من الاسم ، لكننا سنقوم بترقية التعليمات البرمجية بصرامة. لذلك ، بادئ ذي بدء ، انظر إلى هذا التطبيق في شكله الأصلي.

قم بتوسيع helloworld

1. افتح الطرفية وانتقل إلى جذر مجلد JBoss EAP (يمكنك تنزيله هنا ) ، أي في مجلد EAP_HOME.

2. قم بتشغيل خادم JBoss EAP باستخدام ملف التعريف الافتراضي:

$ EAP_HOME/bin/standalone.sh

ملاحظة: في نظام التشغيل Windows ، يتم استخدام البرنامج النصي EAP_HOME \ bin \ standalone.bat للتشغيل.

بعد ثانيتين ، يجب أن يظهر ما يلي في السجل:

[org.jboss.as] (Controller Boot Thread) WFLYSRV0025: JBoss EAP 7.2.0.GA (WildFly Core 6.0.11.Final-redhat-00001) started in 3315ms - Started 306 of 527 services (321 services are lazy, passive or on-demand)

3. افتح المتصفح 127.0.0.1 : 8080 وشاهد هذا:



تين. 1. JBoss EAP الصفحة الرئيسية.

4. اتبع الإرشادات الواردة في دليل إنشاء دليل التشغيل السريع ونشره: نشر helloworld وتنفيذ الأمر التالي (من المجلد الجذر للمشروع):

$ mvn clean install wildfly:deploy

بعد تنفيذ هذا الأمر بنجاح في السجل ، سنرى شيئًا مثل ما يلي:

[INFO] ------------------------------------------------------------------------ 
[INFO] BUILD SUCCESS 
[INFO] ------------------------------------------------------------------------ 
[INFO] Total time: 8.224 s

لذا ، استغرق النشر الأول لتطبيق helloworld على JBoss EAP ما يزيد قليلاً عن 8 ثوانٍ.

نقوم باختبار helloworld

بشكل صارم وفقًا لدليل الوصول إلى التطبيق ، وفتح 127.0.0.1 : 8080 / helloworld في متصفح ونرى ما يلي:



تين. 2. Hello World الأصلي من JBoss EAP.

إجراء التغييرات

تغيير معلمة الإدخال createHelloMessage (اسم السلسلة) من World إلى Marco:

writer.println("<h1>" + helloService.createHelloMessage("Marco") + "</h1>");

مرة أخرى ، قم بتشغيل الأمر التالي:

$ mvn clean install wildfly:deploy

ثم نقوم بتحديث الصفحة في المتصفح ونرى أن النص قد تغير:



تين. 3. مرحبا ماركو في JBoss EAP.

نتراجع عن نشر helloworld ونخرج من JBoss EAP.

هذا اختياري ، ولكن إذا كنت تريد إلغاء النشر ، فيمكنك القيام بذلك باستخدام الأمر التالي:

$ mvn clean install wildfly:undeploy

لإغلاق مثيل JBoss EAP ، ما عليك سوى الضغط على Ctrl + C في نافذة الوحدة الطرفية.

ترقية helloworld


الآن دعونا ترقية تطبيق helloworld الأصلي.

إنشاء فرع جديد

إنشاء فرع عمل جديد بعد انتهاء مشروع البدء السريع:

$ git checkout -b quarkus 7.2.0.GA

تغيير ملف pom.xml

سنبدأ في تغيير التطبيق من ملف pom.xml. حتى يتمكن Quarkus من إدراج كتل XML فيه ، قم بتنفيذ الأمر التالي في مجلد helloworld:

$ mvn io.quarkus:quarkus-maven-plugin:0.23.2:create

عند كتابة هذا المقال ، تم استخدام الإصدار 0.23.2. غالبًا ما تحتوي Quarkus على إصدارات جديدة ؛ يمكنك معرفة الإصدار الأحدث على github.com/quarkusio/quarkus/releases/latest .

سيقوم الأمر أعلاه بإدراج العناصر التالية في pom.xml:

  • خاصية <quarkus.version> التي تحدد إصدار Quarkus المطلوب استخدامه.
  • كتلة <dependencyManagement> لاستيراد Quarkus BOM (قائمة المواد) حتى لا تضيف نسخة لكل تبعية Quarkus.
  • المكوّن الإضافي quarkus-maven-plugin ، المسؤول عن تعبئة التطبيق وتوفير وضع التطوير.
  • ملف تعريف أصلي لإنشاء الملفات التنفيذية للتطبيق.

بالإضافة إلى ذلك ، نقوم يدويًا بإجراء التغييرات التالية على pom.xml:

  1. <groupId> <parent> <artifactId>. <parent>, <groupId>.
  2. <parent>, Quarkus pom JBoss.
  3. <version> <artifactId>. .
  4. <packaging>, WAR, JAR.
  5. :
    1. javax.enterprise:cdi-api io.quarkus:quarkus-arc, <scope>provided</scope>, ( ) Quarkus- injection CDI.
    2. نغير تبعية org.jboss.spec.javax.servlet: jboss-servlet-api_4.0_spec إلى io.quarkus: quarkus-تعهد ، وإزالة <scope> المقدمة </scope> ، لأنه (وفقًا للأرصفة) يوفر ملحق Quarkus دعم servlet 'س.
    3. نزيل تبعية org.jboss.spec.javax.annotation: jboss-annotations-api_1.3_spec ، لأنها تأتي مع التبعيات التي قمنا بتغييرها للتو.


يوجد إصدار ملف pom.xml مع جميع التغييرات على github.com/mrizzi/jboss-eap-quickstarts/blob/quarkus/helloworld/pom.xml .

يرجى ملاحظة أن mvn io.quarkus: quarkus-maven-plugin: 0.23.2: إنشاء أمر لا يغير ملف pom.xml فحسب ، بل يضيف أيضًا عددًا من المكونات إلى المشروع ، وهي الملفات والمجلدات التالية:

  • mvnw and mvnw.cmd .mvn: Maven Wrapper Maven Maven .
  • docker ( src/main/): Dockerfile native jvm ( .dockerignore).
  • resources ( src/main/): application.properties Quarkus index.html ( . Run the modernized helloworld ).

نبدأ helloworld
لاختبار التطبيق ، نستخدم quarkus: dev ، الذي يطلق Quarkus في وضع التطوير (لمزيد من التفاصيل ، راجع هذا القسم في دليل وضع التطوير ).

ملاحظة: من المتوقع أن تؤدي هذه الخطوة إلى حدوث خطأ ، لأننا لم نجري جميع التغييرات اللازمة بعد.

الآن قم بتشغيل الأمر للتحقق من كيفية عمله:

$ ./mvnw compile quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< org.jboss.eap.quickstarts:helloworld >----------------
[INFO] Building Quickstart: helloworld quarkus
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ helloworld ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ helloworld ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- quarkus-maven-plugin:0.23.2:dev (default-cli) @ helloworld ---
Listening for transport dt_socket at address: 5005
INFO  [io.qua.dep.QuarkusAugmentor] Beginning quarkus augmentation
INFO  [org.jbo.threads] JBoss Threads version 3.0.0.Final
ERROR [io.qua.dev.DevModeMain] Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
	[error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: javax.enterprise.inject.spi.DeploymentException: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.jboss.as.quickstarts.helloworld.HelloService and qualifiers [@Default]
	- java member: org.jboss.as.quickstarts.helloworld.HelloWorldServlet#helloService
	- declared on CLASS bean [types=[javax.servlet.ServletConfig, java.io.Serializable, org.jboss.as.quickstarts.helloworld.HelloWorldServlet, javax.servlet.GenericServlet, javax.servlet.Servlet, java.lang.Object, javax.servlet.http.HttpServlet], qualifiers=[@Default, @Any], target=org.jboss.as.quickstarts.helloworld.HelloWorldServlet]
	at io.quarkus.arc.processor.BeanDeployment.processErrors(BeanDeployment.java:841)
	at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:214)
	at io.quarkus.arc.processor.BeanProcessor.initialize(BeanProcessor.java:106)
	at io.quarkus.arc.deployment.ArcProcessor.validate(ArcProcessor.java:249)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at io.quarkus.deployment.ExtensionLoader$1.execute(ExtensionLoader.java:780)
	at io.quarkus.builder.BuildContext.run(BuildContext.java:415)
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1426)
	at java.lang.Thread.run(Thread.java:748)
	at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Caused by: javax.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type org.jboss.as.quickstarts.helloworld.HelloService and qualifiers [@Default]
	- java member: org.jboss.as.quickstarts.helloworld.HelloWorldServlet#helloService
	- declared on CLASS bean [types=[javax.servlet.ServletConfig, java.io.Serializable, org.jboss.as.quickstarts.helloworld.HelloWorldServlet, javax.servlet.GenericServlet, javax.servlet.Servlet, java.lang.Object, javax.servlet.http.HttpServlet], qualifiers=[@Default, @Any], target=org.jboss.as.quickstarts.helloworld.HelloWorldServlet]
	at io.quarkus.arc.processor.Beans.resolveInjectionPoint(Beans.java:428)
	at io.quarkus.arc.processor.BeanInfo.init(BeanInfo.java:371)
	at io.quarkus.arc.processor.BeanDeployment.init(BeanDeployment.java:206)
	... 14 more

لذلك ، لا يعمل ... ولماذا؟

يشير استثناء UnsatisfiedResolutionException إلى فئة HelloService ، وهي عضو في فئة HelloWorldServlet (عضو java: org.jboss.as.quickstarts.helloworld.HelloWorldServlet # helloService). تكمن المشكلة في أن HelloWorldServlet يحتاج إلى مثيل تم حقنه من HelloService ، ولكن لا يمكن العثور عليه (على الرغم من وجود كلا الفئتين في نفس الحزمة).

حان الوقت للعودة إلى الوثائق وقراءة كيفية عمل Quarkusحقنوبالتالي السياقات وحقن التبعية (CDI). لذلك ، نفتح دليل السياقات وحقن التبعية ونقرأ في قسم Bean Discovery : "لا يتم البحث في فئة الفول التي لا تحتوي على تعليق توضيحي يحدد الفول."

نحن ننظر إلى فئة HelloService - ليس لديها مثل هذا التعليق التوضيحي. لذلك ، يجب إضافته حتى يتمكن Quarkus من البحث عن حبة الفول والعثور عليها. ونظرًا لأن هذا كائن بلا حالة ، يمكننا إضافة التعليق التوضيحيApplicationScoped على النحو التالي:

@ApplicationScoped
public class HelloService {

ملاحظة: هنا قد تطلب منك بيئة التطوير إضافة الحزمة المطلوبة (انظر السطر أدناه) ، ويجب القيام بذلك يدويًا ، مثل هذا:

import javax.enterprise.context.ApplicationScoped;

إذا ساورتك الشكوك حول النطاق الذي سيتم استخدامه عند عدم ضبط وحدة المصدر على الإطلاق ، فراجع وثائق JSR 365: السياقات وحق التبعية لـ Java 2.0 - النطاق الافتراضي .

الآن مرة أخرى ، نحاول بدء التطبيق باستخدام الأمر ./mvnw compile quarkus: dev:

$ ./mvnw compile quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------< org.jboss.eap.quickstarts:helloworld >----------------
[INFO] Building Quickstart: helloworld quarkus
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ helloworld ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ helloworld ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/mrizzi/git/forked/jboss-eap-quickstarts/helloworld/target/classes
[INFO]
[INFO] --- quarkus-maven-plugin:0.23.2:dev (default-cli) @ helloworld ---
Listening for transport dt_socket at address: 5005
INFO  [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation
INFO  [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 576ms
INFO  [io.quarkus] (main) Quarkus 0.23.2 started in 1.083s. Listening on: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profile dev activated. Live Coding activated.
INFO  [io.quarkus] (main) Installed features: [cdi]

الآن كل شيء يمر دون أخطاء.

أطلقنا helloworld الذي تمت ترقيته
كما هو مكتوب في السجل ، افتح في المتصفح 0.0.0.0 : 8080 (صفحة بدء Quarkus الافتراضية) وشاهد هذا:



تين. 4. صفحة البداية من Quarkus dev.

يحتوي التعليق التوضيحي WebServlet لهذا التطبيق على تعريف السياق التالي:

@WebServlet("/HelloWorld")
public class HelloWorldServlet extends HttpServlet {

لذلك ، ننتقل إلى 0.0.0.0 : 8080 / HelloWorld في المتصفح ونرى ما يلي:



تين. 5: صفحة مطور Quarkus لتطبيق Hello World.

حسنا ، كل شيء يعمل.

والآن نقوم بإجراء تغييرات على التعليمات البرمجية. لاحظ أن الأمر ./mvnw compile quarkus: dev لا يزال يعمل ولن نوقفه. الآن دعونا نحاول تطبيق نفس التغييرات - التافهة - على الكود نفسه ونرى كيف يجعل Quarkus الحياة أسهل للمطور:

writer.println("<h1>" + helloService.createHelloMessage("Marco") + "</h1>");

احفظ الملف ثم قم بتحديث صفحة الويب لرؤية Hello Marco ، كما هو موضح في لقطة الشاشة أدناه:



تين. 6. مرحبا صفحة ماركو في Quarkus dev.

تحقق الآن من الإخراج في المحطة:

INFO  [io.qua.dev] (vert.x-worker-thread-3) Changed source files detected, recompiling [/home/mrizzi/git/forked/jboss-eap-quickstarts/helloworld/src/main/java/org/jboss/as/quickstarts/helloworld/HelloWorldServlet.java]
INFO  [io.quarkus] (vert.x-worker-thread-3) Quarkus stopped in 0.003s
INFO  [io.qua.dep.QuarkusAugmentor] (vert.x-worker-thread-3) Beginning quarkus augmentation
INFO  [io.qua.dep.QuarkusAugmentor] (vert.x-worker-thread-3) Quarkus augmentation completed in 232ms
INFO  [io.quarkus] (vert.x-worker-thread-3) Quarkus 0.23.2 started in 0.257s. Listening on: http://0.0.0.0:8080
INFO  [io.quarkus] (vert.x-worker-thread-3) Profile dev activated. Live Coding activated.
INFO  [io.quarkus] (vert.x-worker-thread-3) Installed features: [cdi]
INFO  [io.qua.dev] (vert.x-worker-thread-3) Hot replace total time: 0.371s

تسبب تحديث الصفحة في الكشف عن التغييرات في التعليمات البرمجية المصدر ، وقام Quarkus تلقائيًا بإجراء "إيقاف التشغيل". وتم الانتهاء من كل هذا في 0.371 ثانية فقط (ها هو ذا "جافا دون ذرية فائقة السرعة").

نقوم ببناء helloworld في حزمة JAR
الآن بما أن الشفرة تعمل كما ينبغي ، فإننا نحزمها بالأمر التالي:

$ ./mvnw clean package

ينشئ هذا الأمر ملفين JAR في المجلد / target: ملف helloworld-.jar ، وهو عبارة عن قطعة أثرية قياسية تم تجميعها بواسطة فريق Maven مع فئات المشروع وموارده. وملف helloworld هو runner.jar ، وهو ملف JAR قابل للتنفيذ.

لاحظ أن هذه ليست jber-jar ، نظرًا لأنه يتم نسخ جميع التبعيات ببساطة إلى المجلد / target / lib (ولا يتم تجميعها في ملف JAR). لذلك ، لتشغيل JAR هذا من مجلد آخر أو على مضيف مختلف ، تحتاج إلى نسخ كل من ملف JAR والمجلد / lib هناك ، نظرًا لأن عنصر Class-Path في ملف MANIFEST.MF في حزمة JAR يحتوي على قائمة صريحة من JARs من المجلدات lib.
لمعرفة كيفية إنشاء تطبيقات uber-jar ، ارجع إلى البرنامج التعليمي الخاص بإنشاء Uber-Jar .

إطلاق helloworld وتعبئته في JAR

الآن يمكنك تشغيل JAR الخاص بنا باستخدام الأمر java القياسي:

$ java -jar ./target/helloworld-<version>-runner.jar
INFO  [io.quarkus] (main) Quarkus 0.23.2 started in 0.673s. Listening on: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profile prod activated.
INFO  [io.quarkus] (main) Installed features: [cdi]

بعد الانتهاء من كل هذا ، انتقل إلى المتصفح على 0.0.0.0 : 8080 وتحقق من أن كل شيء يعمل كما ينبغي.

وضع helloworld في ملف تنفيذي أصلي

، يعمل helloworld كتطبيق Java مستقل يستخدم تبعيات Quarkus. ولكن يمكنك المضي قدمًا وتحويله إلى ملف قابل للتنفيذ أصلي.

تثبيت GraalVM
أولا وقبل كل شيء، لهذا تحتاج إلى تثبيت الأدوات اللازمة:

1. تحميل GraalVM 19.2.0.1 من github.com/oracle/graal/releases/tag/vm-19.2.0.1 .

2. توسيع الأرشيف الذي تم تنزيله:

$ tar xvzf graalvm-ce-linux-amd64-19.2.0.1.tar.gz

3. انتقل إلى مجلد untar.

4. قم بتشغيل الأمر أدناه لتنزيل وإضافة صورة أصلية:

$ ./bin/gu install native-image

5. نسجل المجلد الذي تم إنشاؤه في الخطوة 2 في متغير البيئة GRAALVM_HOME:

$ export GRAALVM_HOME={untar-folder}/graalvm-ce-19.2.0.1)

لمزيد من المعلومات وإرشادات التثبيت على أنظمة التشغيل الأخرى ، راجع بناء ملف تنفيذي أصلي - المتطلبات الأساسية .

نبني helloworld في ملف تنفيذي أصلي.
نقرأ بناء ملف تنفيذي أصلي - إنتاج دليل تنفيذي أصلي : "الآن قم بإنشاء ملف تنفيذي أصلي لتطبيقنا لتقليل وقت الإطلاق وحجم القرص. سيحتوي الملف القابل للتنفيذ على كل ما يلزم لتشغيل التطبيق ، بما في ذلك جهاز JVM (أو بالأحرى ، نسخته المقتطعة التي تحتوي فقط على ما هو مطلوب لتشغيل التطبيق) والتطبيق نفسه. "

لإنشاء ملف تنفيذي أصلي ، يجب تمكين ملف تعريف Maven الأصلي:

$ ./mvnw package -Pnative

استغرق تجميعنا دقيقة واحدة و 10 ثوانٍ ، وتم إنشاء ملف helloworld النهائي - العداء f في المجلد / target.

نبدأ helloworld للملف القابل للتنفيذ الأصلي

في الخطوة السابقة حصلنا على الملف القابل للتنفيذ / target / helloworld - runner. شغلها الآن:

$ ./target/helloworld-<version>-runner
INFO  [io.quarkus] (main) Quarkus 0.23.2 started in 0.006s. Listening on: http://0.0.0.0:8080
INFO  [io.quarkus] (main) Profile prod activated.
INFO  [io.quarkus] (main) Installed features: [cdi]

مرة أخرى ، افتح المتصفح 0.0.0.0 : 8080 وتحقق من أن كل شيء يعمل كما ينبغي.

يتبع!

نحن نعتقد أن طريقة تحديث تطبيقات Java باستخدام إمكانيات Quarkus التي تم النظر فيها في هذا المنشور (وإن كان مع أبسط مثال) يجب أن يتم تطبيقها بنشاط في الحياة الواقعية. في هذه الحالة ، من المحتمل أن تواجه عددًا من المشاكل ، سننظر في حلها جزئيًا في المشاركة التالية ، والتي ستركز على كيفية قياس استهلاك الذاكرة من أجل تقييم تحسينات الأداء ، وهو جزء مهم من عملية تحديث التطبيق بأكملها.

All Articles