إعداد Spring والملفّات الشخصية

ملفات تعريف البيئة (@Profile)

18 دقيقة الدرس 6 من 13

ملفات تعريف البيئة (@Profile)

كل تطبيق جاد يعمل في أكثر من بيئة واحدة: حاسوب المطوّر المحلي، وخادم ضمان الجودة المشترك، وبيئة الاختبار المرحلي (staging)، والإنتاج. تحتاج كل بيئة عادةً إلى بنية تحتية مختلفة — قاعدة بيانات H2 في الذاكرة محليًا، ومثيل PostgreSQL حقيقي في الاختبار المرحلي، وبيانات اعتماد مشفّرة في الإنتاج. تكرار فئات الإعداد لكل بيئة أو إخفاء كل اختلاف خلف غابة من جمل if أمر هش ومعرّض للأخطاء.

تحلّ آلية ملفات التعريف (profiles) في Spring هذه المشكلة بأناقة. الملف التعريفي هو تجميع منطقي مُسمّى. تُعلّق الـ beans أو فئات الإعداد بأكملها بـ @Profile، وتُفعّل ملفًا تعريفيًا واحدًا أو أكثر عند بدء التشغيل، فيُسجّل Spring فقط الـ beans التي تتطابق ملفاتها التعريفية. كل ما عداها يُتجاهل — بأمان، على مستوى الحاوية، لا بشروط وقت التشغيل المبعثرة في كودك.

إعلان Bean مرتبط بملف تعريفي محدد

تقبل التعليقة التوضيحية @Profile سلسلة نصية واحدة أو مصفوفة من السلاسل. الـ bean المُعلَّق بـ @Profile("dev") لا يُسجَّل إلا عندما يكون ملف التعريف dev نشطًا.

import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import javax.sql.DataSource; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; @Configuration public class DataSourceConfig { @Bean @Profile("dev") public DataSource devDataSource() { // قاعدة بيانات سريعة في الذاكرة — لا حاجة لـ Docker أو VPN محليًا return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .addScript("classpath:schema.sql") .addScript("classpath:test-data.sql") .build(); } @Bean @Profile("prod") public DataSource prodDataSource() { // تجمّع اتصال حقيقي يشير إلى Postgres في الإنتاج var config = new com.zaxxer.hikari.HikariConfig(); config.setJdbcUrl(System.getenv("DB_URL")); config.setUsername(System.getenv("DB_USER")); config.setPassword(System.getenv("DB_PASS")); config.setMaximumPoolSize(20); return new com.zaxxer.hikari.HikariDataSource(config); } }

يُقيّم Spring التعليقة @Profile عند بدء تشغيل الحاوية. إن لم يتطابق أي ملف تعريفي نشط، فلن يُسجَّل الـ bean ببساطة. وإن حاول كودك حقن DataSource دون محدِّد ولم يكن أيّ ملف تعريفي نشطًا، ستحصل على NoSuchBeanDefinitionException عند بدء التشغيل — وهو تمامًا نمط الفشل الصحيح: صاخب ومبكّر، قبل تقديم أي طلب.

تعليق فئة إعداد بأكملها

عندما تكون فئة كاملة ذات صلة ببيئة واحدة فقط، عليق الفئة نفسها بدلًا من كل طريقة @Bean على حدة. هذا أكثر وضوحًا ويجعل القصد جليًا:

@Configuration @Profile("staging") public class StagingEmailConfig { @Bean public JavaMailSender mailSender() { // يشير إلى مثيل MailHog/Mailpit محلي — لن تغادر الرسائل الشبكة JavaMailSenderImpl sender = new JavaMailSenderImpl(); sender.setHost("localhost"); sender.setPort(1025); return sender; } @Bean public String supportEmail() { return "staging-support@internal.example.com"; } }
@Profile على الفئة مقابل الطريقة: حين يكون @Profile على الفئة، ترث كل طريقة @Bean بداخلها هذا الملف التعريفي. حين يكون على طريقة @Bean واحدة، يُقيَّد ذلك الـ bean وحده. استخدم التعليق على مستوى الفئة حين يكون الإعداد بأكمله خاصًا ببيئة ما؛ واستخدم التعليق على مستوى الطريقة حين تمزج بين beans مشتركة وأخرى خاصة بملف تعريفي في الفئة ذاتها.

تعبيرات ملف التعريف — دمج الملفات التعريفية منطقيًا

منذ Spring 5.1، تدعم السلسلة النصية داخل @Profile لغة تعبير بسيطة تتيح لك كتابة شروط دون الحاجة إلى تعليقات متعددة:

  • @Profile("dev") — نشط حين يكون dev نشطًا.
  • @Profile("!prod") — نشط حين لا يكون prod نشطًا (مفيد لحواجز الأمان).
  • @Profile("dev | staging") — نشط حين يكون أيٌّ منهما نشطًا (منطق OR).
  • @Profile("cloud & eu-west") — نشط فقط حين يكون كلاهما نشطَين (منطق AND).
  • @Profile("(dev | staging) & !legacy") — تعبيرات منطقية مُجمَّعة.
@Bean @Profile("!prod") public DataSource mockPaymentGateway() { // سجّل دائمًا بوابة دفع وهمية ما لم نكن في الإنتاج. // يمنع هذا الرسوم الحقيقية غير المقصودة في أي بيئة غير إنتاجية، // حتى لو نسي أحدهم تفعيل ملف التعريف "dev" صراحةً. return new MockPaymentDataSource(); }
استخدم حواجز النفي للـ beans الحرجة أمنيًا. تعليق stub أو mock بـ @Profile("!prod") أكثر دفاعية من تعليق التنفيذ الحقيقي بـ @Profile("prod"). إن شغّل أحدهم التطبيق دون أي ملف تعريفي، يُحمَّل الـ stub — وهو أكثر أمانًا بكثير من تحميل تكامل الإنتاج عن طريق الخطأ.

تفعيل ملفات التعريف

يقرأ Spring الملفات التعريفية النشطة من عدة مصادر، يُقيَّم كلٌّ منها وفق الأولوية. أكثر الآليات شيوعًا:

1. في application.properties / application.yml:

# application.properties spring.profiles.active=dev
# application.yml spring: profiles: active: staging

هذا مريح للإعدادات المحلية الافتراضية لكنه لا ينبغي أن يُودَع في نظام التحكم بالإصدار وهو يشير إلى prod — وإلا سيُشير كل مطوّر يستنسخ المستودع إلى الإنتاج عن غير قصد.

2. كخاصية نظام JVM (الأعلى أولوية من سطر الأوامر):

java -jar myapp.jar -Dspring.profiles.active=prod

3. كمتغير بيئة لنظام التشغيل (المفضّل للحاويات وCI/CD):

SPRING_PROFILES_ACTIVE=prod java -jar myapp.jar

تتّبع متغيرات البيئة قواعد الربط المرنة (relaxed-binding) في Spring Boot: تصبح النقاط شرطات سفلية وتُكتب الأسماء بحروف كبيرة. هذا يتوافق مع اصطلاحات Docker وKubernetes ومعظم منصات CI، مما يجعله الآلية الإنتاجية المعيارية للتفعيل.

4. برمجيًا — مفيد في الاختبارات:

import org.springframework.test.context.ActiveProfiles; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest @ActiveProfiles("test") class PaymentServiceIntegrationTest { // يُفعّل Spring ملف التعريف "test" لهذه الفئة الاختبارية فقط }

يمكن تفعيل ملفات تعريف متعددة في آنٍ واحد. افصل بينها بفواصل في الخاصية أو متغير البيئة، أو مرّر قيمًا متعددة إلى @ActiveProfiles:

SPRING_PROFILES_ACTIVE=cloud,eu-west,feature-x

ملفات الخصائص الخاصة بكل ملف تعريفي

يُمدّد Spring Boot آلية ملفات التعريف لتشمل ملفات الخصائص تلقائيًا. إن كان ملف التعريف النشط هو staging، يحمّل Boot كلًّا من application.properties وapplication-staging.properties (أو ما يعادلهما من YAML). تتجاوز قيم الملف الخاص بالملف التعريفي قيم الملف الأساسي.

# application.properties (الإعدادات الافتراضية المشتركة) server.port=8080 logging.level.root=INFO # application-dev.properties (تجاوزات المطوّر) server.port=8090 logging.level.com.example=DEBUG spring.datasource.url=jdbc:h2:mem:devdb # application-prod.properties (قيم الإنتاج — لا تودع الأسرار هنا) logging.level.root=WARN management.endpoints.web.exposure.include=health,info
لا تودع أسرار الإنتاج أبدًا في ملفات الخصائص الخاصة بملفات التعريف. application-prod.properties في نظام التحكم بالإصدار هو المكان المناسب للإعدادات الإنتاجية غير السرية (مستويات التسجيل، كشف نقاط النهاية)، لكن كلمات مرور قواعد البيانات ومفاتيح API يجب أن تأتي من متغيرات البيئة أو مدير أسرار — حتى في ملفات الخصائص الخاصة بالملفات التعريفية.

ملف التعريف الافتراضي

الـ beans المُعلَّقة بـ @Profile("default") تُسجَّل حين لا يكون أي ملف تعريفي نشطًا. هذا شبكة أمان مفيدة: قدّم بديلًا معقولًا (عادةً stub محلي) حتى يبدأ التطبيق بنظافة حتى دون ملف تعريفي صريح، بدلًا من إلقاء خطأ bean مفقود.

@Bean @Profile("default") public DataSource fallbackDataSource() { // يُحمَّل حين لا يُضبط أي ملف تعريفي — مفيد لتشغيلات IDE بدون أعلام -D return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .build(); }

الخلاصة

تمنحك التعليقة @Profile طريقة Spring-managed أصيلة لتنويع رسم الـ beans حسب البيئة. علّق الـ beans أو فئات الإعداد بأكملها باسم ملف تعريفي؛ استخدم صياغة التعبيرات (!، |، &) لشروط أكثر ثراءً. فعّل ملفات التعريف عبر متغيرات البيئة للحاويات، وعبر application.properties للإعدادات الافتراضية للمطوّرين، وعبر @ActiveProfiles في الاختبارات. ادمج @Profile مع ملفات الخصائص الخاصة بكل ملف تعريفي (application-{profile}.properties) لتبقي إعداد كل بيئة مُجمَّعًا وقابلًا للقراءة وخاليًا من جمل if في وقت التشغيل.