
لا يوجد حاليًا نقص في الأطر لإنشاء خدمات ميكروية في Java و Kotlin. يناقش المقال ما يلي:
بناءً عليها ، تم إنشاء أربع خدمات يمكنها التفاعل مع بعضها البعض من خلال واجهة برمجة تطبيقات HTTP باستخدام نمط اكتشاف الخدمة المطبق باستخدام
القنصل . وبالتالي ، فإنها تشكل بنية غير متجانسة (على مستوى الإطار) microservice (المشار إليها فيما يلي باسم ISA):

حدد مجموعة من المتطلبات لكل خدمة:
- كومة التكنولوجيا:
- JDK 12 ؛
- Kotlin.
- Gradle (Kotlin DSL) ؛
- يونيت 5.
- وظيفة (HTTP API):
GET /application-info{?request-to=some-service-name}
إرجاع بعض المعلومات الأساسية حول الخدمة المجهرية (الاسم ، الإطار ، سنة إصدار الإطار) ؛ عند تحديد اسم إحدى الخدمات الميكروية الأربعة في معلمة request-to
إلى HTTP API الخاصة به ، يتم تنفيذ طلب مماثل بإرجاع المعلومات الأساسية ؛GET /application-info/logo
إرجاع الصورة.
- التنفيذ:
- الإعداد باستخدام ملف التكوين ؛
- باستخدام حقن التبعية
- الاختبارات التي تتحقق من وظائف HTTP API.
- ISA:
- باستخدام نمط اكتشاف الخدمة (التسجيل في القنصل ، الوصول إلى HTTP API لجهاز microservice آخر باسمه باستخدام موازنة تحميل العميل) ؛
- تشكيل قطعة أثرية جرة.
بعد ذلك ، نفكر في تنفيذ خدمة microservice على كل من الأطر ومقارنة معلمات التطبيقات المستلمة.
خدمة هيليدون
تم إنشاء إطار التطوير في Oracle للاستخدام الداخلي ، وبالتالي أصبح مفتوح المصدر. يوجد نموذجان لتطوير هذا الإطار: Standard Edition (SE) و MicroProfile (MP). في كلتا الحالتين ، ستكون الخدمة عبارة عن برنامج Java SE منتظم. تعرف على المزيد حول الاختلافات في
هذه الصفحة.
باختصار ، Helidon MP هو أحد تطبيقات Eclipse
MicroProfile ، التي تجعل من الممكن استخدام العديد من واجهات برمجة التطبيقات ، وكلاهما معروف سابقًا لمطوري Java EE (على سبيل المثال ، JAX-RS ، CDI) ، وأحدث التطبيقات (التحقق من الصحة ، المقاييس ، خطأ التسامح ور. د.). في متغير Helidon SE ، تم إرشاد المطورين بمبدأ "No magic" ، والذي يتم التعبير عنه ، على وجه الخصوص ، في التعليقات التوضيحية الأقل أو معدومة اللازمة لإنشاء التطبيق.
تم اختيار Helidon SE لتطوير الخدمات المصغرة. من بين أمور أخرى ، فإنه يفتقر إلى أدوات لتنفيذ Dependency Injection ، لذلك
يتم استخدام
Koin لتنفيذ التبعيات. التالية هي فئة تحتوي على الطريقة الرئيسية. لتنفيذ حقن التبعية ، يرث الفصل من
KoinComponent . يبدأ Koin أولاً ، ثم تتم تهيئة التبعيات المطلوبة ويتم
startServer()
طريقة
startServer()
، حيث يتم إنشاء كائن من نوع
WebServer ، حيث يتم نقل إعدادات التطبيق والتوجيه مسبقًا ؛ بعد البدء ، يتم تسجيل التطبيق في القنصل:
object HelidonServiceApplication : KoinComponent { @JvmStatic fun main(args: Array<String>) { val startTime = System.currentTimeMillis() startKoin { modules(koinModule) } val applicationInfoService: ApplicationInfoService by inject() val consulClient: Consul by inject() val applicationInfoProperties: ApplicationInfoProperties by inject() val serviceName = applicationInfoProperties.name startServer(applicationInfoService, consulClient, serviceName, startTime) } } fun startServer( applicationInfoService: ApplicationInfoService, consulClient: Consul, serviceName: String, startTime: Long ): WebServer { val serverConfig = ServerConfiguration.create(Config.create().get("webserver")) val server: WebServer = WebServer .builder(createRouting(applicationInfoService)) .config(serverConfig) .build() server.start().thenAccept { ws -> val durationInMillis = System.currentTimeMillis() - startTime log.info("Startup completed in $durationInMillis ms. Service running at: http://localhost:" + ws.port())
تم تكوين التوجيه على النحو التالي:
private fun createRouting(applicationInfoService: ApplicationInfoService) = Routing.builder() .register(JacksonSupport.create()) .get("/application-info", Handler { req, res -> val requestTo: String? = req.queryParams() .first("request-to") .orElse(null) res .status(Http.ResponseStatus.create(200)) .send(applicationInfoService.get(requestTo)) }) .get("/application-info/logo", Handler { req, res -> res.headers().contentType(MediaType.create("image", "png")) res .status(Http.ResponseStatus.create(200)) .send(applicationInfoService.getLogo()) }) .error(Exception::class.java) { req, res, ex -> log.error("Exception:", ex) res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send() } .build()
يستخدم التطبيق التكوين في تنسيق
HOCON :
webserver { port: 8081 } application-info { name: "helidon-service" framework { name: "Helidon SE" release-year: 2019 } }
من الممكن أيضًا استخدام الملفات بتنسيقات JSON و YAML وخصائص التهيئة (مزيد من التفاصيل
هنا ).
خدمة كتور
هو مكتوب الإطار في Kotlin. يمكن إنشاء مشروع جديد بعدة طرق: استخدام نظام
الإنشاء أو
start.ktor.io أو
المكون الإضافي لـ IntelliJ IDEA (المزيد
هنا ).
مثل Helidon SE ، لا يوجد لدى Ktor DI خارج الصندوق ، لذلك يتم تطبيق التبعيات باستخدام Koin قبل بدء تشغيل الخادم:
val koinModule = module { single { ApplicationInfoService(get(), get()) } single { ApplicationInfoProperties() } single { ServiceClient(get()) } single { Consul.builder().withUrl("http://localhost:8500").build() } } fun main(args: Array<String>) { startKoin { modules(koinModule) } val server = embeddedServer(Netty, commandLineEnvironment(args)) server.start(wait = true) }
يتم تحديد الوحدات المطلوبة من قبل التطبيق في ملف التكوين (من الممكن استخدام تنسيق HOCON فقط ؛ المزيد حول تكوين خادم Ktor
هنا ) ، يتم تقديم محتوياته أدناه:
ktor { deployment { host = localhost port = 8082 watch = [io.heterogeneousmicroservices.ktorservice] } application { modules = [io.heterogeneousmicroservices.ktorservice.module.KtorServiceApplicationModuleKt.module] } } application-info { name: "ktor-service" framework { name: "Ktor" release-year: 2018 }
يستخدم Ktor و Koin المصطلح "module" ، الذي له معان مختلفة. في Koin ، الوحدة النمطية تمثيلية لسياق التطبيق في Spring Framework. الوحدة النمطية Ktor هي وظيفة معرفة من قبل المستخدم وتقبل كائنًا من النوع
Application ويمكنها تكوين خط أنابيب ، وتعيين الميزات ، وطرق التسجيل ، والعملية
الطلبات ، الخ:
fun Application.module() { val applicationInfoService: ApplicationInfoService by inject() if (!isTest()) { val consulClient: Consul by inject() registerInConsul(applicationInfoService.get(null).name, consulClient) } install(DefaultHeaders) install(Compression) install(CallLogging) install(ContentNegotiation) { jackson {} } routing { route("application-info") { get { val requestTo: String? = call.parameters["request-to"] call.respond(applicationInfoService.get(requestTo)) } static { resource("/logo", "logo.png") } } } }
في مقتطف الشفرة هذا ، يتم تكوين توجيه الطلبات ، على وجه الخصوص ،
logo.png
الثابت.
قد تحتوي خدمة Ktor على ميزات. الميزة هي وظيفة مضمنة في
خط أنابيب استجابة الطلب (
DefaultHeaders ، ضغط ، وغيرها في مثال التعليمة البرمجية أعلاه). من الممكن تطبيق ميزاتك الخاصة ، على سبيل المثال ، تنفذ الشفرة أدناه نمط اكتشاف الخدمة بالاقتران مع موازنة تحميل العميل بناءً على خوارزمية Round-robin:
class ConsulFeature(private val consulClient: Consul) { class Config { lateinit var consulClient: Consul } companion object Feature : HttpClientFeature<Config, ConsulFeature> { var serviceInstanceIndex: Int = 0 override val key = AttributeKey<ConsulFeature>("ConsulFeature") override fun prepare(block: Config.() -> Unit) = ConsulFeature(Config().apply(block).consulClient) override fun install(feature: ConsulFeature, scope: HttpClient) { scope.requestPipeline.intercept(HttpRequestPipeline.Render) { val serviceName = context.url.host val serviceInstances = feature.consulClient.healthClient().getHealthyServiceInstances(serviceName).response val selectedInstance = serviceInstances[serviceInstanceIndex] context.url.apply { host = selectedInstance.service.address port = selectedInstance.service.port } serviceInstanceIndex = (serviceInstanceIndex + 1) % serviceInstances.size } } } }
يوجد المنطق الرئيسي في طريقة
install
: أثناء مرحلة طلب
التقديم (التي تعمل قبل مرحلة
consulClient
، يتم تحديد اسم الخدمة التي يتم الاتصال بها أولاً ، ثم يتم طلب قائمة
consulClient
هذه الخدمة من
consulClient
، وبعد ذلك يتم تحديد المثيل باستخدام خوارزمية Round-robin. وبالتالي ، تصبح المكالمة التالية ممكنة:
fun getApplicationInfo(serviceName: String): ApplicationInfo = runBlocking { httpClient.get<ApplicationInfo>("http://$serviceName/application-info") }
خدمة Micronaut
تم تطوير Micronaut من قبل المبدعين في
Grails framework وهو مستوحى من تجربة خدمات البناء باستخدام Spring، Spring Boot and Grails. إطار العمل متعدد اللغات يدعم Java و Kotlin و Groovy ؛
ربما سيكون
هناك دعم لسكالا. يتم تنفيذ حقن التبعية في مرحلة الترجمة ، مما يؤدي إلى انخفاض استهلاك الذاكرة وبدء تشغيل التطبيق بشكل أسرع مقارنةً بـ Spring Boot.
الطبقة الرئيسية لديها النموذج التالي:
object MicronautServiceApplication { @JvmStatic fun main(args: Array<String>) { Micronaut.build() .packages("io.heterogeneousmicroservices.micronautservice") .mainClass(MicronautServiceApplication.javaClass) .start() } }
تتشابه بعض مكونات تطبيق يستند إلى Micronaut مع نظيراتها في تطبيق Spring Boot ، على سبيل المثال ، يكون رمز وحدة التحكم كما يلي:
@Controller( value = "/application-info", consumes = [MediaType.APPLICATION_JSON], produces = [MediaType.APPLICATION_JSON] ) class ApplicationInfoController( private val applicationInfoService: ApplicationInfoService ) { @Get fun get(requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo) @Get("/logo", produces = [MediaType.IMAGE_PNG]) fun getLogo(): ByteArray = applicationInfoService.getLogo() }
يعتمد دعم Kotlin في Micronaut على
المكون الإضافي لبرنامج التحويل البرمجي
kapt (مزيد من
المعلومات هنا ). يتم تكوين البرنامج النصي التجميع كما يلي:
plugins { ... kotlin("kapt") ... } dependencies { kapt("io.micronaut:micronaut-inject-java") ... kaptTest("io.micronaut:micronaut-inject-java") ... }
فيما يلي محتويات ملف التكوين:
micronaut: application: name: micronaut-service server: port: 8083 consul: client: registration: enabled: true application-info: name: ${micronaut.application.name} framework: name: Micronaut release-year: 2018
تكوين Microservice ممكن أيضًا مع JSON وخصائص وتنسيقات ملفات Groovy (مزيد من التفاصيل
هنا ).
خدمة التمهيد الربيع
تم إنشاء إطار العمل لتبسيط تطوير التطبيقات باستخدام نظام Spring Framework البيئي. يتم تحقيق ذلك من خلال آليات التكوين التلقائي عند توصيل المكتبات. ما يلي هو رمز تحكم:
@RestController @RequestMapping(path = ["application-info"], produces = [MediaType.APPLICATION_JSON_UTF8_VALUE]) class ApplicationInfoController( private val applicationInfoService: ApplicationInfoService ) { @GetMapping fun get(@RequestParam("request-to") requestTo: String?): ApplicationInfo = applicationInfoService.get(requestTo) @GetMapping(path = ["/logo"], produces = [MediaType.IMAGE_PNG_VALUE]) fun getLogo(): ByteArray = applicationInfoService.getLogo() }
تم تكوين microservice مع ملف YAML:
spring: application: name: spring-boot-service server: port: 8084 application-info: name: ${spring.application.name} framework: name: Spring Boot release-year: 2014
من الممكن أيضًا استخدام ملفات تنسيق الخصائص للتكوين (مزيد من التفاصيل
هنا ).
إطلاق
يعمل المشروع على JDK 12 ، على الرغم من أنه من المحتمل أن يكون في الإصدار 11 أيضًا ، تحتاج فقط إلى تغيير معلمة
jvmTarget
في البرامج النصية للتجميع
jvmTarget
:
withType<KotlinCompile> { kotlinOptions { jvmTarget = "12" ... } }
قبل بدء خدمات micros ، تحتاج إلى
تثبيت القنصل
وبدء تشغيل الوكيل - على سبيل المثال ، مثل:
consul agent -dev
.
يمكن بدء تشغيل خدمات micros من:
بعد بدء تشغيل جميع الخدمات المصغرة على
http://localhost:8500/ui/dc1/services
سترى:

اختبار API
يتم إعطاء نتائج اختبار API Helidon service كمثال:
GET http://localhost:8081/application-info
{ "name": "helidon-service", "framework": { "name": "Helidon SE", "releaseYear": 2019 }, "requestedService": null }
GET http://localhost:8081/application-info?request-to=ktor-service
{ "name": "helidon-service", "framework": { "name": "Helidon SE", "releaseYear": 2019 }, "requestedService": { "name": "ktor-service", "framework": { "name": "Ktor", "releaseYear": 2018 }, "requestedService": null } }
GET http://localhost:8081/application-info/logo
إرجاع الصورة.
يمكنك اختبار واجهة برمجة تطبيقات microservice التعسفية باستخدام
Postman (
مجموعة من الطلبات) أو
عميل IntelliJ IDEA
HTTP (
مجموعة من الطلبات) أو مستعرض أو أداة أخرى. إذا كنت تستخدم العميلين الأولين ، فأنت بحاجة إلى تحديد منفذ الخدمة microservice في المتغير المقابل (في Postman يكون في
قائمة المجموعة -> تحرير -> المتغيرات ، وفي HTTP Client يكون في متغير البيئة المحدد في
هذا الملف) ، وعند اختبار الطريقة 2) تحتاج واجهة برمجة التطبيقات أيضًا إلى تحديد اسم الخدمة الميكروية المطلوبة "أسفل الغطاء". الإجابات ستكون مماثلة لتلك المذكورة أعلاه.
مقارنة إعدادات التطبيق
حجم قطعة أثرية
من أجل الحفاظ على بساطة تكوين التطبيقات وتشغيلها في البرامج النصية للتجميع ، لم يتم استبعاد أي تبعيات متعدية ، وبالتالي فإن حجم خدمة uber-JAR على Spring Boot يتجاوز بشكل كبير حجم التناظرية على أطر أخرى (لأنه عند استخدام المبتدئين ، لا يتم استيراد التبعيات الضرورية فقط ؛ إذا رغبت في ذلك ، يمكن تقليل الحجم بشكل كبير):
وقت الاطلاق
وقت الإطلاق لكل تطبيق غير متسق ويقع في بعض "النافذة" ؛ يوضح الجدول أدناه وقت إطلاق الأداة دون تحديد أي معلمات إضافية:
تجدر الإشارة إلى أنه إذا قمت بتنظيف تطبيق Spring Boot من التبعيات غير الضرورية وتنبه إلى تهيئة التطبيق لبدء التشغيل (على سبيل المثال ، تفحص الحزم الضرورية فقط واستخدام تهيئة صندوق الكسل) ، يمكنك تقليل وقت بدء التشغيل بشكل كبير.
اختبار الحمل
للاختبار ، تم استخدام
Gatling و Scala
script . تم تشغيل مولد الحمل والخدمة قيد الاختبار على نفس الجهاز (Windows 10 ، معالج رباعي النواة 3.2 جيجا هرتز ، 24 جيجابايت من ذاكرة الوصول العشوائي ، SSD). يشار إلى منفذ هذه الخدمة في سكالا سكريبت.
لكل ميكروسيرفيس:
- الحد الأدنى لمقدار الذاكرة المؤقتة (
-Xmx
) المطلوب لتشغيل خدمة -Xmx
تعمل (استجابة للطلبات) - الحد الأدنى لذاكرة الكومة المطلوبة لاجتياز اختبار الحمل 50 مستخدمًا * 1000 طلب
- الحد الأدنى لذاكرة الكومة المطلوبة لاجتياز اختبار الحمل 500 مستخدم * 1000 طلب
إن اجتياز اختبار الحمل يعني أن الخدمة الميكروية استجابت لجميع الطلبات في أي وقت.
تجدر الإشارة إلى أن جميع الخدمات المصغرة تستخدم خادم Netty HTTP.
استنتاج
المهمة - إنشاء خدمة بسيطة مع HTTP API والقدرة على العمل في ISA - كانت قادرة على أن تكتمل على جميع الأطر المعنية. حان الوقت للتقييم والنظر في إيجابيات وسلبيات.
Helidonالطبعة القياسية- الايجابيات
- إعدادات التطبيق
من جميع النواحي أظهرت نتائج جيدة. - "لا سحر"
يبرر الإطار المبدأ الذي @JvmStatic
: لقد @JvmStatic
تعليق توضيحي واحد فقط لإنشاء التطبيق ( @JvmStatic
- @JvmStatic
بين Java و Kotlin).
- سلبيات
- microframework
بعض المكونات الضرورية للتنمية الصناعية مفقودة ، على سبيل المثال ، حقن التبعية وتنفيذ خدمة الاكتشاف.
MicroProfileلم يتم تطبيق Microservice على هذا الإطار ، لذلك سألاحظ فقط بضع نقاط أعرفها:
- الايجابيات
- تطبيق Eclipse MicroProfile
في الجوهر ، MicroProfile هو Java EE المُحسّن لـ ISA. وبالتالي ، أولاً ، يمكنك الوصول إلى مجموعة كاملة من واجهات برمجة تطبيقات Java EE ، بما في ذلك التطبيقات المصممة خصيصًا لـ ISA ، وثانياً ، يمكنك تغيير تطبيق MicroProfile إلى أي تطبيق آخر (Open Liberty ، WildFly Swarm ، وما إلى ذلك) .
- بالإضافة إلى ذلك
- في MicroProfile Starter ، يمكنك إنشاء مشروع من البداية باستخدام المعلمات الضرورية عن طريق القياس باستخدام أدوات مشابهة لأطر أخرى (على سبيل المثال ، Spring Initializr ). في وقت نشر المقال ، تنفذ Helidon برنامج MicroProfile 1.2 ، في حين أن أحدث إصدار من المواصفات هو 3.0.
Ktor- الايجابيات
- خفة
يسمح لك بالاتصال فقط بالوظائف المطلوبة مباشرة لإكمال المهمة ؛ - إعدادات التطبيق
نتائج جيدة في جميع النواحي.
- سلبيات
- "شحذ" تحت Kotlin ، أي أنه من الممكن ، ولكن ليس من الضروري ، تطوير في Java ؛
- أعمال مصغره (انظر البند مماثلة ل Helidon SE).
- بالإضافة إلى ذلك
من ناحية ، لم يتم تضمين مفهوم تطوير الإطار في أكثر نماذج تطوير Java شيوعًا (Spring-like (Spring Boot / Micronaut) و Java EE / MicroProfile) ، مما قد يؤدي إلى:
- مشكلة في العثور على المتخصصين ؛
- وقت أطول لإكمال المهام مقارنةً بـ Spring Boot بسبب الحاجة إلى تكوين الوظيفة المطلوبة بشكل صريح.
من ناحية أخرى ، فإن الاختلاف بين "الربيع" الكلاسيكي و Java EE يتيح لك النظر إلى عملية التطوير من زاوية مختلفة ، وربما بوعي أكبر.
Micronaut- الايجابيات
- عثمان
كما ذكرنا سابقًا ، تتيح لك AOT تقليل وقت البدء والذاكرة التي يستهلكها التطبيق مقارنةً بنظيره في Spring Boot ؛ - نموذج تطوير يشبه الربيع
المبرمجون الذين لديهم خبرة في تطوير برنامج Spring لن يستغرقوا الكثير من الوقت لإتقان هذا الإطار - إعدادات التطبيق
نتائج جيدة في جميع النواحي ؛ - متعدد اللغات
دعم مواطن من الدرجة الأولى لجافا وكوتلين وغروفي ؛ ربما سيكون هناك دعم لسكالا. في رأيي ، يمكن أن يؤثر هذا بشكل إيجابي على نمو المجتمع. بالمناسبة ، في شهر يونيو من عام 2019 ، احتلت شركة TIOBE المرتبة 14 في ترتيب شعبية شعبية لغات البرمجة ، حيث حصلت على المركز الثاني بعد 60 عامًا ، وبذلك أصبحت في المرتبة الثانية بين لغات JVM ؛ - يتيح لك مشروع Micronaut for Spring أيضًا تغيير وقت تشغيل تطبيق Spring Boot الحالي إلى Micronaut (مع قيود).
التمهيد الربيع- الايجابيات
- منصة النضج والنظام الإيكولوجي
إطار "كل يوم". بالنسبة لمعظم المهام اليومية ، يوجد بالفعل حل في نموذج برمجة Spring ، أي بطريقة مألوفة لدى العديد من المبرمجين. تم تبسيط التطوير من خلال مفاهيم البداية والتكوينات التلقائية. - وجود عدد كبير من المتخصصين في سوق العمل ، فضلاً عن وجود قاعدة معرفية كبيرة (بما في ذلك الوثائق والأجوبة على Stack Overflow) ؛
- وجهة نظر
أعتقد أن الكثيرين سيوافقون على أن الربيع سيظل في المستقبل القريب إطار التطوير الرائد.
- سلبيات
- إعدادات التطبيق
لم يكن التطبيق على هذا الإطار من بين القادة ، ومع ذلك ، يمكن تحسين بعض المعلمات ، كما ذكر سابقًا ، بشكل مستقل. تجدر الإشارة أيضًا إلى وجود مشروع Spring Fu ، الذي هو قيد التطوير النشط ، والذي يسمح باستخدامه بالحد من هذه المعايير.
يمكنك أيضًا تسليط الضوء على المشكلات العامة المرتبطة بالأُطر الجديدة المفقودة من Spring Boot:
- نظام بيئي أقل تطوراً ؛
- عدد قليل من المتخصصين ذوي الخبرة في هذه التقنيات ؛
- وقت أطول لإكمال المهام ؛
- آفاق غامضة.
تنتمي الأطر المدروسة إلى فئات مختلفة للوزن: Helidon SE و Ktor عبارة عن إطارين مصغرين ، Spring Spring هو إطار مكدس كامل ، Micronaut على الأرجح أيضًا مكدس كامل؛ هناك فئة أخرى هي MicroProfile (مثل Helidon MP). في الأشكال المصغرة ، تكون الوظيفة محدودة ، مما قد يؤدي إلى إبطاء تنفيذ المهام ؛ لتوضيح إمكانية تنفيذ هذه الوظيفة أو تلك على أساس أي إطار تطوير ، نوصي بأن تتعرف على مستنداتها.
لا أجرؤ على الحكم على ما إذا كان هذا أو ذاك الإطار "سيطلق النار" في المستقبل القريب ، لذلك ، في رأيي ، من الأفضل الاستمرار في مراقبة تطور الأحداث باستخدام إطار التطوير الحالي لحل مهام العمل.
في الوقت نفسه ، كما هو موضح في المقالة ، تتفوق الأطر الجديدة على Boot Spring بواسطة المعلمات التي تم اعتبارها للتطبيقات المستلمة. إذا كانت أي من هذه المعلمات ضرورية لأي من خدمات microservices الخاصة بك ، فقد تحتاج إلى الانتباه إلى الأطر التي أظهرت أفضل النتائج عليها. ومع ذلك ، لا تنسَ أن برنامج Spring Boot ، أولاً ، يواصل التحسن ، وثانيًا ، لديه نظامًا بيئيًا ضخمًا وأن عددًا كبيرًا من مبرمجي Java على دراية به. هناك أطر أخرى لم يتم تغطيتها في هذه المقالة: جافالين ، كواركوس ، إلخ.
يمكنك عرض رمز المشروع على
جيثب . شكرا لاهتمامكم!
ملاحظة: بفضل
artglorin للمساعدة في هذه المقالة.