Salesforce 解锁包:使用 DX 简化开发与部署

背景与应用场景

在传统的 Salesforce 开发模式中,组织(Org)中的元数据(Metadata)管理和部署通常依赖于变更集(Change Set)或 Ant 迁移工具(Ant Migration Tool)。这些工具在面对复杂项目、多团队协作以及持续集成/持续部署(CI/CD)的需求时,往往暴露出效率低下、错误率高和难以版本控制等弊端。

为了应对这些挑战,Salesforce 推出了 Salesforce DX (Salesforce Developer Experience),这是一种集成的端到端开发工具套件,旨在提升开发人员的生产力和协作效率。Salesforce DX 引入了源代码驱动的开发(Source-Driven Development)、临时组织(Scratch Org)、Dev Hub (开发中心) 等核心概念,并在此基础上,将解锁包(Unlocked Packages)作为其实现模块化和可重用组件的关键机制。

解锁包作为 Salesforce DX 的核心组成部分,允许开发人员将应用程序功能分解为可管理的、独立部署的模块。其主要应用场景包括:

  • 模块化开发: 将大型应用程序拆分为多个更小、更易于管理的功能单元,每个单元可以独立开发、测试和部署。
  • 多团队协作: 不同团队可以负责开发不同的解锁包,减少元数据冲突,提高并行开发效率。
  • 共享库和组件: 创建可重用的基础组件或功能库,供组织内其他项目或解锁包使用。
  • 持续集成/持续部署 (CI/CD): 解锁包与版本控制系统(如 Git)紧密集成,可以自动化地构建、测试和部署应用程序,加速开发周期。
  • 应用程序的独立升级: 允许独立更新应用程序的特定功能模块,而无需影响整个应用程序。

通过解锁包,Salesforce 开发团队可以摆脱传统部署模式的束缚,拥抱更现代、更敏捷的开发实践。

原理说明

解锁包(Unlocked Packages)是 Salesforce DX 提供的一种包类型,用于将元数据(Metadata)组件(如 Apex 类、Visualforce 页面、Lightning Web 组件、自定义对象等)打包成一个独立的、可部署的单元。它介于非受管包(Unmanaged Packages)和受管包(Managed Packages)之间,结合了两者的优点,并克服了它们的局限性。

Unlocked Packages 与其他包类型的区别:

  • 非受管包 (Unmanaged Packages): 一旦安装,所有元数据都成为目标组织的本地元数据,无法升级,发布者也无法控制。适用于开源项目或简单的组件共享。
  • 受管包 (Managed Packages): 主要用于 ISV (独立软件供应商) 将应用程序分发给客户,提供知识产权保护、命名空间隔离和自动升级能力。安装后,部分元数据不可编辑。
  • 解锁包 (Unlocked Packages): 兼具可升级性(像受管包)和安装后元数据可编辑性(像非受管包)。它提供了一种轻量级的模块化方式,适用于企业内部开发或 ISV 应用程序的开发阶段。解锁包不提供知识产权保护,也不强制命名空间,但强烈建议使用命名空间以避免冲突。

解锁包的关键特性:

  1. 模块化: 将应用程序分解为独立的、功能明确的模块。
  2. 可版本化: 每个解锁包可以有多个版本,每个版本都是不可变的(Immutable)。通过安装不同的版本可以实现升级或降级。
  3. 可升级: 可以在目标组织中安装新版本的解锁包,从而更新其包含的元数据。
  4. 元数据可编辑: 安装解锁包后,其包含的元数据在目标组织中是可编辑的。这意味着管理员或开发人员可以根据需要修改这些组件。
  5. 依赖管理: 解锁包支持显式声明对其他解锁包或基线组织元数据的依赖关系,确保部署顺序和兼容性。
  6. 源代码驱动: 解锁包的创建和管理完全基于 Salesforce DX 的源代码驱动开发模式,与版本控制系统无缝集成。
  7. Dev Hub (开发中心) 驱动: 创建和管理解锁包需要在 Dev Hub 组织中进行。Dev Hub 存储了所有解锁包及其版本的元数据。

解锁包的工作流程:

开发人员首先在 Salesforce DX 项目中定义包目录和其包含的元数据。然后,通过 Salesforce CLI (命令行界面) 命令执行以下步骤:

  1. 创建包: 在 Dev Hub 中注册一个新的解锁包定义。这会为包分配一个唯一的 ID。
  2. 创建包版本: 根据项目中的元数据生成一个不可变的包版本。每个版本都有一个唯一的版本号。
  3. 安装包版本: 将特定的包版本安装到临时组织、沙盒(Sandbox)或生产组织(Production Org)中。
  4. 升级包版本: 在已安装包的组织中,安装新的包版本来更新其功能。

解锁包通过将应用程序逻辑分解为独立的单元,大大简化了复杂应用程序的开发、测试和部署过程,是构建现代化 Salesforce 解决方案不可或缺的工具。


示例代码

本节将通过一系列 Salesforce CLI (sfdx) 命令示例,演示如何创建、管理和部署解锁包。所有代码示例均严格遵循 Salesforce 官方文档。

1. 创建 Salesforce DX 项目

首先,我们需要创建一个 Salesforce DX 项目。这是一个基于本地文件系统的项目,用于管理您的元数据和解锁包定义。

sfdx force:project:create --projectname MyUnlockedPackageProject --outputdir . --template standard

注释:

  • sfdx force:project:create:用于创建一个新的 Salesforce DX 项目。
  • --projectname MyUnlockedPackageProject:指定项目的名称。
  • --outputdir .:将项目创建在当前目录下。
  • --template standard:使用标准项目模板,它包含一个 force-app 目录用于存放元数据。

进入新创建的项目目录:

cd MyUnlockedPackageProject

2. 将元数据转换为包目录结构

sfdx-project.json 文件中定义包目录。例如,我们假设你有一个自定义对象 MyObject__c 和一个 Apex 类 MyService

编辑 sfdx-project.json 文件,添加一个 packageDirectories 条目。假设我们的包元数据在 force-app/main/default 目录下。

{
  "packageDirectories": [
    {
      "path": "force-app",
      "default": true,
      "package": "MyAwesomePackage",
      "versionName": "ver 1.0",
      "versionNumber": "1.0.0.NEXT",
      "definitionFile": "config/project-scratch-def.json"
    }
  ],
  "namespace": "",
  "sfdcLoginUrl": "https://login.salesforce.com",
  "sourceApiVersion": "58.0"
}

注释:

  • packageDirectories:定义了项目中的包目录。
  • path: "force-app":指定包元数据所在的目录路径。
  • default: true:表示这是默认的包目录。
  • package: "MyAwesomePackage":这是我们稍后要创建的解锁包的别名(alias),这个名字将用于 CLI 命令中。
  • versionName: "ver 1.0":包版本的名称。
  • versionNumber: "1.0.0.NEXT":包版本号。NEXT 是一个占位符,每次创建新版本时会自动递增。
  • sourceApiVersion:项目使用的 API 版本。

确保你的元数据(例如 MyObject__c.object-meta.xml, MyService.cls, MyService.cls-meta.xml)已经放置在 force-app/main/default 目录下,例如:

MyUnlockedPackageProject/
├── force-app/
│   └── main/
│       └── default/
│           ├── objects/
│           │   └── MyObject__c/
│           │       └── MyObject__c.object-meta.xml
│           └── classes/
│               ├── MyService.cls
│               └── MyService.cls-meta.xml
├── config/
│   └── project-scratch-def.json
├── .sfdx/
├── sfdx-project.json
└── README.md

3. 创建解锁包

在 Dev Hub (开发中心) 中注册一个新的解锁包定义。这将创建一个包别名 MyAwesomePackage 及其对应的 ID。

sfdx force:package:create --name "MyAwesomePackage" --path force-app --packagetype Unlocked --description "An awesome unlocked package for my project"

注释:

  • sfdx force:package:create:创建包定义。
  • --name "MyAwesomePackage":包的名称,这个名称将显示在 Dev Hub 中,与 sfdx-project.json 中的 package 字段匹配。
  • --path force-app:指定包元数据所在的目录路径。
  • --packagetype Unlocked:明确指定创建的包类型为解锁包。
  • --description "...":为包提供一个描述。

成功执行后,您会在命令行输出中看到一个 Package Id,并且 sfdx-project.json 文件中的 packageDirectories 字段会自动更新,将 package 字段的值替换为这个 Package Id。

{
  "packageDirectories": [
    {
      "path": "force-app",
      "default": true,
      "package": "0HoXXXXXXXXXXXXXXX", // Example Package ID
      "versionName": "ver 1.0",
      "versionNumber": "1.0.0.NEXT",
      "definitionFile": "config/project-scratch-def.json"
    }
  ],
  "namespace": "",
  "sfdcLoginUrl": "https://login.salesforce.com",
  "sourceApiVersion": "58.0"
}

注意: 每次运行 sfdx force:package:create 都会创建一个新的包。通常只需要运行一次。如果您的 sfdx-project.json 中已经有包 ID,则不需要再次运行此命令,除非您想创建不同的新包。

4. 创建解锁包版本

将当前项目的元数据打包成一个不可变的解锁包版本。这是部署到其他组织的基础。

sfdx force:package:version:create --package "MyAwesomePackage" --installationkeybypass --wait 10 --json

注释:

  • sfdx force:package:version:create:创建包的新版本。
  • --package "MyAwesomePackage":指定要为其创建版本的包的别名(来自 sfdx-project.json)。也可以直接使用包 ID (0HoXXXXXXXXXXXXXXX)。
  • --installationkeybypass:跳过安装密钥的设置。在开发阶段这很方便。如果需要设置密钥,请使用 --installationkey YourKey
  • --wait 10:等待包版本创建完成的最长时间(分钟)。
  • --json:以 JSON 格式输出结果,便于脚本处理。

成功执行后,您会得到一个 Package Version Id (例如 04tXXXXXXXXXXXXXXX)。您需要记录这个 ID,因为它将用于安装包。

sfdx-project.json 文件中的 versionNumber 会自动更新,将 1.0.0.NEXT 替换为实际的版本号,并在 packageDirectories 数组中添加一个 versionNumberversionName 对应的 version 数组元素。

5. 安装解锁包版本

将创建的包版本安装到目标组织(例如 Scratch Org、Sandbox 或 Production Org)。

sfdx force:org:create --definitionfile config/project-scratch-def.json --setalias MyScratchOrg --durationdays 7 --setdefaultusername
sfdx force:package:install --package 04tXXXXXXXXXXXXXXX --targetusername MyScratchOrg --wait 10 --publishwait 10

注释:

  • 第一行(sfdx force:org:create):创建并授权一个 Scratch Org。
  • --definitionfile config/project-scratch-def.json:使用定义的 Scratch Org 配置文件。
  • --setalias MyScratchOrg:为 Scratch Org 设置一个别名。
  • --setdefaultusername:将此 Scratch Org 设置为默认的用户名。
  • sfdx force:package:install:安装包版本。
  • --package 04tXXXXXXXXXXXXXXX:指定要安装的包版本 ID。
  • --targetusername MyScratchOrg:指定目标组织别名。
  • --wait 10:等待安装完成的最长时间(分钟)。
  • --publishwait 10:等待所有异步发布回调完成的最长时间(分钟)。

6. 升级解锁包版本

当您修改了元数据并创建了新的包版本后,可以通过以下命令升级已安装的解锁包。

假设您修改了 MyObject__c,并创建了一个新的包版本(例如 1.1.0,其 Package Version Id 为 04tYYYYYYYYYYYYYYY)。

sfdx force:package:install --package 04tYYYYYYYYYYYYYYY --targetusername MyScratchOrg --wait 10 --publishwait 10 --upgradetype Mixed

注释:

  • sfdx force:package:install:同样使用安装命令进行升级。
  • --package 04tYYYYYYYYYYYYYYY:指定新的包版本 ID。
  • --upgradetype Mixed:升级类型。
    • Mixed:默认值。合并新的元数据,保留用户对现有元数据的修改。
    • Delete:删除包中不再存在的元数据。谨慎使用
    • DeprecateOnly:将包中已存在的元数据标记为已弃用,但不会删除。

注意事项

在使用 Salesforce 解锁包进行开发和部署时,有几个关键的注意事项需要牢记,以确保项目的顺利进行和长期维护。

1. 元数据覆盖范围 (Metadata Coverage)

并非所有的 Salesforce 元数据类型都支持以包(包括解锁包)的形式进行打包。在开始使用解锁包之前,务必查阅 Salesforce 官方的Metadata Coverage Report,确认您的关键元数据类型是否受支持。如果某个元数据类型不支持打包,您可能需要考虑将其作为“基线元数据”(Org-Dependent Metadata)来管理,或者寻找替代方案。

2. 依赖管理 (Dependency Management)

解锁包支持声明依赖关系。如果您的解锁包依赖于其他解锁包或组织中已存在的特定元数据(例如,某个自定义对象或字段),您需要在 sfdx-project.json 文件中明确声明这些依赖。未声明的依赖可能导致安装失败或运行时错误。

{
  "packageDirectories": [
    {
      "path": "force-app",
      "default": true,
      "package": "0HoXXXXXXXXXXXXXXX",
      "versionName": "ver 1.0",
      "versionNumber": "1.0.0.NEXT",
      "definitionFile": "config/project-scratch-def.json",
      "dependencies": [
        {
          "package": "AnotherUnlockedPackage@1.0.0-1" // 依赖另一个解锁包及其版本
        },
        {
          "subscriberPackageVersionId": "04tYYYYYYYYYYYYYYY" // 或者直接依赖包版本ID
        }
      ]
    }
  ],
  "namespace": "",
  "sfdcLoginUrl": "https://login.salesforce.com",
  "sourceApiVersion": "58.0"
}

⚠️ 未找到官方文档支持: dependencies 字段中直接指定 subscriberPackageVersionId 的方式在 CLI 官方文档中提及,但其用法和具体的包别名或版本号的映射可能需要根据实际情况验证。通常建议使用包别名和版本号进行依赖声明。

确保依赖包在主包安装前已安装,且版本兼容。

3. 命名空间 (Namespace)

尽管解锁包不强制使用命名空间,但对于大型项目或共享组件而言,使用命名空间是避免元数据命名冲突的最佳实践。它还能提供一定程度的隔离性。如果决定使用命名空间,请在创建 Dev Hub 组织时进行设置,并在 sfdx-project.json 中指定。一旦设置,命名空间无法更改。

4. 升级与降级行为 (Upgrade and Downgrade Behavior)

  • 升级: 当安装一个新版本的解锁包时,默认行为是合并新的元数据,并尝试保留现有组织中对元数据的修改。然而,如果新版本删除了旧版本中存在的元数据,则需要使用 --upgradetype Delete 才能真正删除。
  • 降级: 解锁包支持降级,即安装一个比当前已安装版本更旧的版本。但这并不会自动回滚元数据的更改。如果旧版本不包含新版本中存在的元数据,这些新元数据将保留在组织中。因此,降级操作需要谨慎,并进行充分测试。
  • 销毁性更改(Destructive Changes): 如果您的新包版本移除了旧版本中的元数据,并且您希望这些元数据从目标组织中删除,您需要在升级时使用 --upgradetype Delete。对于复杂的销毁性更改,可能需要手动编写销毁性元数据 XML 文件,并通过非包机制(如 Ant Migration Tool 或 SFDX Source Deploy)进行处理。

5. 权限和配置文件 (Permissions and Profiles)

解锁包可以包含权限集(Permission Sets)和配置文件(Profiles)的元数据。然而,配置文件是组织特定的,通常不推荐直接打包完整的配置文件。相反,建议使用权限集来管理对包中组件的访问权限,因为权限集更具模块化和可移植性。

6. 错误处理与日志 (Error Handling and Logging)

在创建包版本或安装包时,CLI 会提供详细的输出。仔细检查这些输出,尤其是错误信息。对于 CI/CD 管道,将 CLI 输出记录下来至关重要,以便于调试和故障排除。

7. Dev Hub (开发中心)

Dev Hub 组织是创建和管理解锁包的中心。所有解锁包定义及其版本都存储在 Dev Hub 中。确保您的 Dev Hub 组织配置正确,并有足够的包创建限制。

8. 临时组织 (Scratch Org) 的使用

强烈建议在 Scratch Org 中开发和测试解锁包。Scratch Org 提供了干净、可重复的环境,可以模拟不同组织的状态,确保包的独立性和可移植性。


总结与最佳实践

Salesforce 解锁包是 Salesforce DX 生态系统中的一个强大工具,它极大地提升了 Salesforce 应用程序的开发、部署和维护效率。通过模块化、版本控制和可升级性,解锁包使得团队能够更敏捷、更协作地构建和交付复杂的解决方案。

总结:

解锁包允许我们将 Salesforce 应用程序分解为独立的、可部署的单元,每个单元都可以拥有自己的生命周期。它在受管包和非受管包之间提供了一个理想的平衡点,既提供了受管包的可升级性,又保留了非受管包的灵活性(元数据安装后可编辑)。这使得解锁包成为企业内部开发、多团队协作以及构建共享功能库的理想选择。

最佳实践:

  1. 拥抱源代码驱动开发: 将所有元数据存储在版本控制系统(如 Git)中,并将其作为真理的唯一来源。解锁包的创建和管理应始终从源代码开始。
  2. 坚持模块化设计原则: 将应用程序分解为逻辑上独立的、职责单一的解锁包。避免创建过于庞大或耦合度过高的包。每个包应解决一个特定的业务问题或提供一组相关的功能。
  3. 利用临时组织 (Scratch Orgs): 使用 Scratch Orgs 进行开发和测试。每个开发人员都应该在隔离的 Scratch Org 中工作,然后将更改推送到版本控制系统,并通过解锁包进行构建和部署。这确保了开发环境的一致性和可重复性。
  4. 自动化 CI/CD 管道: 建立一个强大的 CI/CD 管道来自动化解锁包的构建、测试和部署。这包括:
    • 从版本控制系统拉取最新代码。
    • 在 Scratch Org 中部署代码并运行自动化测试。
    • 创建解锁包版本。
    • 在集成或 UAT (用户验收测试) 沙盒中安装和测试包版本。
    • 最终将包部署到生产环境。
  5. 合理管理依赖关系: 明确声明解锁包之间的依赖关系,并在 sfdx-project.json 中进行配置。确保依赖包在被依赖包安装之前已经存在于目标组织中,并保持版本兼容性。
  6. 优先使用权限集 (Permission Sets): 避免直接打包配置文件。相反,通过权限集来管理对包中组件的访问权限,因为权限集更具模块化和可移植性。
  7. 严格的版本管理: 维护清晰的包版本命名约定(如语义化版本控制:MAJOR.MINOR.PATCH)。每次发布到集成或生产环境都应创建一个新的包版本。
  8. 考虑命名空间: 对于大型或复杂的解决方案,考虑为您的解锁包使用命名空间,以避免元数据命名冲突并提供更好的隔离。
  9. 持续测试: 对解锁包中的所有代码和功能进行全面的自动化测试。在每次包版本创建和部署之前运行测试,以确保代码质量和稳定性。

通过遵循这些最佳实践,您的团队可以充分发挥 Salesforce 解锁包的潜力,构建出更健壮、更灵活、更易于维护的 Salesforce 应用程序。

评论

此博客中的热门博文

Salesforce Experience Cloud 技术深度解析:构建社区站点 (Community Sites)

Salesforce 登录取证:深入解析用户访问监控与安全

Salesforce Data Loader 全方位指南:数据迁移与管理的最佳实践