Try Serpent Free
Andrew Hanna

Andrew Hanna

2026-04-07T03:50:41.998Z

أسرار Salesforce Second Generation Managed Package

أسرار Salesforce Second Generation Managed Package

سنتناول عدة موضوعات تجعل عملك أسهل، سواء كنت تستخدم بالفعل second generation managed packages أو كنت جديدا على هذا الموضوع.

عندما تنشئ إصدارا جديدا، قد يستغرق ذلك الكثير من الوقت والجهد، وعند فشله لا يقدم أحيانا رسائل خطأ مفيدة بما يكفي لمساعدتك على حل المشكلة. كنت أتمنى حقا أن يقدم رسائل خطأ أكثر تفصيلا مثل أمر sfdx deploy. ومع ذلك، فهذا ليس الجزء الأصعب بعد. يصبح الأمر أكثر تعقيدا عندما تكون الحزمة قد أُنشئت وأصبحت جاهزة للاستخدام، لكن عند محاولة تثبيتها في test org تكتشف أن الكثير من الميزات لا تعمل كما هو متوقع.

إذا كنت تريد تجنب هذا الارتباك أو تواجهه بالفعل، فأنت في المكان الصحيح لتتعلم من تحدياتنا وأخطائنا وتوفر على نفسك صداعا غير ضروري.

معدلات الوصول في Apex

لنبدأ بالتعرف إلى معدلات الوصول المختلفة في كلاسات Apex حتى تساعدك على اختيار طريقة تنفيذ الكود:

Private

هذا هو معدل الوصول الافتراضي، ويعني أن method أو variable متاح فقط داخل كلاس Apex الذي تم تعريفه فيه. إذا لم تحدد معدل وصول، فستكون method أو variable خاصة.

Protected

يعني ذلك أن method أو variable مرئي لأي inner classes داخل كلاس Apex المعرِّف، وكذلك للكلاسات التي ترث من ذلك الكلاس. يمكنك استخدام معدل الوصول هذا فقط مع instance methods وmember variables. هذا الإعداد أكثر سماحية من الإعداد الافتراضي private، تماما كما في Java.

Public

يعني ذلك أن method أو variable متاح لكل Apex داخل package محددة.

في Apex، لا يعمل معدل الوصول public بالطريقة نفسها التي يعمل بها في Java. تم ذلك لتقليل ربط التطبيقات ببعضها وإبقاء كود كل تطبيق منفصلا. في Apex، إذا أردت جعل شيء ما عاما كما في Java، فيجب استخدام معدل الوصول global.

@NamespaceAccessible annotation

لإتاحة الوصول لكل second-generation (2GP) managed packages التي تشترك في namespace، استخدم public مع annotation @NamespaceAccessible. استخدام معدل الوصول public في packages من دون namespace يجعل كود Apex ضمنيا @NamespaceAccessible.

Global

يعني ذلك أن method أو variable يمكن أن يستخدمه أي كود Apex لديه وصول إلى الكلاس، وليس فقط كود Apex داخل التطبيق نفسه. يجب استخدام معدل الوصول هذا لأي method يجب الرجوع إليها من خارج التطبيق، سواء عبر SOAP API أو بواسطة كود Apex آخر. إذا أعلنت method أو variable كـ global، فيجب أيضا إعلان الكلاس الذي يحتويها كـ global.

نوصي باستخدام معدل الوصول global نادرا، إن كان لا بد من استخدامه. فالاعتماديات بين التطبيقات صعبة الصيانة.

AuraEnabled Annotation

تمكّن annotation @AuraEnabled الوصول من جهة العميل وجهة الخادم إلى Apex controller method. إضافة هذه annotation تجعل methods لديك متاحة لمكونات Lightning، سواء Lightning web components أو Aura components. لا يتم كشف إلا methods التي تحمل هذه annotation.

في API version 44.0 وما بعده، يمكنك تحسين أداء runtime عبر تخزين نتائج method على العميل باستخدام annotation @AuraEnabled(cacheable=true). يمكنك تخزين نتائج methods فقط للـ methods التي تسترجع البيانات ولا تعدلها. استخدام هذه annotation يلغي الحاجة إلى استدعاء setStorable() في JavaScript code عند كل action يستدعي Apex method.

في API version 55.0 وما بعده، يمكنك استخدام annotation @AuraEnabled(cacheable=true scope='global') لتمكين تخزين Apex methods في global cache.

إذا كان لديك كلاس Apex في base package لديك 

global with sharing class TestClass{ 
@AuraEnabled global static void testMethod{} }
يمكنك الرجوع إلى هذه method من package مختلفة داخل Lightning Web Component بهذه الطريقة

import logExceptionLWC from "@salesforce/apex/TestClass.testMethod";
معدل الوصول global هو الحيلة التي تمكّنك من الرجوع إليه من package أخرى. لكن إذا كنت ترجع إلى Lightning Web Component من Apex Class داخل package نفسها، فسيكون معدل الوصول Public مناسبا في هذه الحالة.

JSON Access Annotation

من أهم الحيل أنه عندما تنشئ model مخصصا في كود Apex وقد تحتاج لاحقا إلى serialize أو deserialize له، فستحتاج بالتأكيد إلى إضافة Json Access annotation للـ custom model. تلميح: تأكد من إضافة annotation إلى child objects أيضا ;)

إذن ما Json Access annotation؟ وما خيارات واعتبارات serialization و deserialization الخاصة بها؟

تتحكم annotation @JsonAccess المعرفة على مستوى Apex class فيما إذا كان يمكن serialize أو deserialize لنسخ الكلاس. إذا قيدت annotation عمليات JSON serialization وdeserialization، فسيتم رمي runtime JSONException.

تفرض المعلمتان serializable وdeserializable في annotation @JsonAccess السياقات التي يسمح فيها Apex بعمليات serialization وdeserialization.

يمكنك تحديد معلمة واحدة أو كلتيهما، لكن لا يمكنك تحديد annotation دون معلمات. القيم الصالحة للمعلمات التي تحدد ما إذا كانت serialization و deserialization مسموحتين:

  • never: غير مسموح أبدا
  • sameNamespace: مسموح فقط لكود Apex داخل namespace نفسها
  • samePackage: مسموح فقط لكود Apex داخل package نفسها، ويؤثر فقط في second-generation packages
  • always: مسموح دائما لأي كود Apex

اعتبارات JSON Access

إذا تم توسيع Apex class يحمل JsonAccess، فلن يرث الكلاس الموسع هذه الخاصية. إذا طُبقت method toString على objects يجب ألا يتم serialize لها، فقد يتم كشف بيانات خاصة. يجب عليك override للـ toString method على objects التي يجب حماية بياناتها. مثلا، عند serialize لكائن مخزن كمفتاح في Map، يتم استدعاء toString method. تتضمن الخريطة الناتجة entries من key (string) وvalue، وبذلك تكشف كل حقول الكائن.

@JsonAccess(serializable='always' deserializable='always')
global class FullDetails{
  global String fullName {get; set;} 
  global Address address {get; set;} 

@JsonAccess(serializable='always' deserializable='always')
global class Address{
  global String streetName {get; set;} 
  global String postalCode {get; set;} 
  }
}

الرجوع إلى Apex Classes من Managed Package

ماذا لو كنت تنشئ بعض apex classes غير المعبأة داخل مشروعك، وقد تحتاج إلى الرجوع إلى apex class آخر أو interface يمثل جزءا من managed package؟ تأكد فقط من تعيين هذه الكلاسات كـ global والرجوع إلى الكلاس عبر <Namespace>.<ClassName>

Named Credentials

عادة ما يكون مرجع callout هو callout:<NamedCredentialsApiName>، لكن إذا كنت تستخدم named credentials كجزء من package لكلاسات callout، فستحتاج إلى تحديثه إلى callout:<NampeSpace>__<NamedCredentialsApiName>

Connected App

إذا كنت تضيف مكون connected app إلى package لديك، فهذه هي الخطوات الدقيقة للقيام بذلك وتجنب بعض الأخطاء الشائعة.

  1. أنشئ developer org
  2. انتقل إلى package manager
  3. تحقق من توفر namespace ثم أضفها
  4. اربط namespace developer org بـ DevHub لديك. إذا فشل الاتصال بعد نافذة allow access، فانتظر من 10 إلى 15 دقيقة ثم حاول مرة أخرى.
  5. انتقل إلى app manager وأنشئ connected app جديدة
  6. عيّن إما full permissions أو api وweb وrefresh permissions
  7. بعد إنشاء connected app، يمكنك إضافة metadata داخل مشروع dx
<?xml version="1.0" encoding="UTF-8"?>
<ConnectedApp xmlns="http://soap.sforce.com/2006/04/metadata">
  <developerName>YourNamespace__ConnectedAppName</developerName>
  <contactEmail>ConnectedAppContactEmail</contactEmail>
  <label>ConnectedAppLabelName</label>
  <version>ConnectedAppVersion</version>
</ConnectedApp>

إذا اتبعت الخطوات نفسها بالضبط، وفشلت أثناء إنشاء package version مع الخطأ التالي

ERROR running force:package:version:create: <ConnectedAppName>: Installing an app (<ConnectedAppName>) that has been deleted.

صدقني، مررنا بذلك. ستحتاج إلى إنشاء package جديدة أولا، ثم إنشاء package version جديدة. للتحقق من ذلك قبل البدء، أنشئ مشروع dx تجريبيا جديدا وأنشئ package جديدة، وأضف connected app نفسها التي أنشأتها مسبقا، ثم أنشئ package version وأخبرني هل نجح الأمر معك.

بقية مكونات Salesforce

أخيرا، تحتاج إلى مراجعة كل الحقول والكائنات وtriggers وpermission sets وcustom metadata وغيرها، وإضافة <Namespace>__

تحديات وحيل تثبيت Managed Package

إذا وجدت خطأ مشابها يؤدي إلى فشل تثبيت package في أي بيئة، فبدلا من قضاء ساعات وأيام في محاولة فهمه، أزل Quick Actions القياسية من layout ثم أعد إضافتها بعد تثبيت package بنجاح.

(ObjectName-Layout) In field: QuickAction - no QuickAction named NewCase found
(ObjectName-Layout) In field: QuickAction - no QuickAction named NewTask found
(ObjectName-Layout) In field: QuickAction - no QuickAction named LogACall found
Component [flexipage:filterListCard] attribute [filterName]: Error retrieving filter [My_ChatterGroups] for entity [CollaborationGroup]

هناك أمر آخر يجب التفكير فيه: إذا كان source code لديك يعتمد على Salesforce built-in Components، فتأكد من منح الوصول إلى تلك المكونات كجزء من خطوات pre-deployment قبل تثبيت package version. مثل هنا، تفعيل omni channel.

Your org doesn't have access to component runtime_service_omnichannel:omniWidget.

أخيرا، إذا كانت لديك standardValueSets في source code وكان metadata الخاص بـ package version يعتمد عليها، فتأكد من نشر تلك value sets كخطوة pre-deployment قبل تثبيت package version. مثل هنا، نشر قيمة pick-list مخصصة باسم TestValue.

Component [lst:dynamicRelatedList] attribute [adminFilters]: TestValue isn't a valid picklist value.

يمكنك الرجوع إلى Metadata Coverage Documentation لمعرفة أي المكونات يمكن تضمينها في package.

يسعدنا مشاركة أي معلومات مفيدة ونوصيك بمعرفة المزيد عن تأثيرنا في دورة الإصدارات في Salesforce

احجز عرضا توضيحيا لمعرفة المزيد عن كيفية تمكين Serpent لاستراتيجية Salesforce DevOps لديك اليوم. Serpent من Tekunda يقدم أدوات ممتازة لمساعدة فرق DevOps لديك، بما في ذلك التحكم المدمج في الإصدارات، وأنظمة النشر المستمر والإصدارات، وأدوات الدمج، والعديد من أدوات الاختبار المختلفة.

مقالات ذات صلة

هل تريد معرفة كيف يمكنكم الشحن أسرع قبل البدء؟ لنتحدث

بدون التزام.