【BluePrint】用OSGi 应用程序开发和工作的最佳实践
?简介:?十多年来,OSGi 技术已经解决了围绕复杂性、可扩展性和可维护性的应用程序开发模块性挑战。随着 IBM? WebSphere? Application Server Feature Pack for OSGi Applications and JPA 2.0 的引入,OSGi bundles 构成的企业级 Java? 应用程序现在可以被开发和部署到 WebSphere Application Server V7。本文介绍了开发构造良好的 OSGi 应用程序的最佳实践,来帮助您从这个新功能获取最大效益。 本文来自于 IBM WebSphere Developer Technical Journal 中文版。
?
?
OSGi 模块性提供了标准机制来以 Java 应用程序应对共同挑战。在 2007 年,OSGi Alliance Enterprise Expert Group (EEG) 成立,以一个业务 Java 编程模型的形式向业务应用程序开发人员引入 OSGi 基础设施。OSGi 应用程序和 IBM WebSphere Application Server 企业级服务质量共同为模块化 Web 应用程序提供最完整和最健壮的业务服务器。您可以使用 WebSphere Application Server Feature Pack for OSGi Applications and JPA 2.0 来部署和管理 Web 应用程序,作为一组版本 OSGi bundle。您也可以配置一个或多个 bundle 存储库,作为供应技术设施部分,来承载多个应用程序使用的公共 bundle 和简化使用这些公共 bundle 的应用程序部署。WebSphere Application Server V7 Feature Pack for SCA V1.0.1.5 升级版添加了对由异构资产组成的 OSGi 应用程序的支持,以支持面向服务体系结构(SOA)概念。(见 参考资料)
对于任何新技术都有一些该做的和不该做的建议,对架构师、开发人员和部署人员来说这也称为最佳实践。OSGi 技术已经使用了十多年,并且在那时出现了许多最佳实践。本文介绍了为 OSGi Applications feature of WebSphere Application Server 编写 OSGi 应用程序和集成 Service Component Architecture (SCA) 相关的主要最佳实践。其中一些是常用 OSGi 最佳实践,一些是专用于 WebSphere Application Server 中提供支持的;为了清晰起见,属于后者的最佳实践已指明。
?
?
图 1 显示了一个 API bundle、一个提供服务的实现和一个使用来自实现 bundle 的客户端 bundle。客户端 bundle 和实现 bundle 在 API bundle 上都有一个包依赖项(package dependency),其中含有服务 Java 界面。客户端 bundle 是 OSGi 应用程序(在内容中列出)的一部分,API 和实现 bundle 是共享的,都是来自于内部 bundle 存储库。API bundle 将配置到 WebSphere Application Server 作为包依赖的结果,如果客户端 bundle 和实现 bundle 都是使用 Blueprint 实现的,那么实现 bundle 也将被配置。如果这两个 bundle 中有一个不使用 Blueprint,那么将不配置实现 bundle 。
如果,实现 bundle 使用 Blueprint,而客户端不使用,那么也提供应用程序,但是没有实现 bundle 。这是因为供应程序没有意识到客户端 bundle 缺失服务依赖。如果客户端 bundle 使用 Blueprint 但是实现 bundle 没有使用,那么应用程序将不能部署,因为供应程序不能满足客户端 bundle 服务依赖。
?
图 2 显示了一个 OSGi 应用程序和一个 bundle,提供了一个由 SCA 从应用程序外部调用的服务。这个调用来自另一个组件(似乎是另一个 OSGi 应用程序或一个 Java EE 应用程序),或者来自一个特定的绑定,例如一个 Web 服务或 JMS 调用。同一个 bundle 也需要一个由 SCA 提供的外部应用程序服务。这可能再一次调用另一个 SCA 组件,或在一个特定的绑定上进行一次调用。在图 2 的第一个图表中,Bundle A 是使用 Blueprint 实现的。SCA 使用 Blueprint 服务定义来了解如何分辨和调用目标 OSGi 服务。SCA 不使用 Blueprint 服务参考定义,因为在应用程序清单(用于定义一个 OSGi 应用程序的构件)中有充足信息来确定什么样的服务是有效目标。在第二个图表中,服务和参考资料在 Bundle B 中使用其他组件模型实现,例如,声明服务—B)。SCA 不能理解 DS,因此这是无效的。在第 3 个图表中,一个 Blueprint 虚包 bundle(Bundle C )用于向 SCA 描述服务,然后将调用转发到 Bundle B 中的 DS 服务实现。
?
图 4 显示了不同版本中的两个 API 提供商。也显示了次版本中的一个改变如何分别影响客户端和实现者。既然这样,您可以提供 API 的1.0 版本和 1.1 版本。客户 A 可以使用任何一个 API 提供程序;客户 B 使用新 API 方法,可使用最高版本 1.1。Implementation A 使用 bundle API A 提供的 API,而 Implementation B 只使用 bundle API B 提供的 API 。
服务注册表将对请求者可见的服务限制在执行与包绑定的 API 版本的那些请求者范围内,知道这一点很重要。
图 4. 一个次要 API 版本改变如何不同程度地影响客户端和实现
?
图 5 从客户和实现者角度显示了一个不兼容的 API 改变。既然这样,Implementation A 和 Client A 被绑定到 API A,Implementation B 和 Client B 绑定到 API B。
图 5. 一个主要 API 版本改变如何同样影响客户端和实现
?
?
?
?
另一个场景如图 7 所示,其中显示了一个客户端,从一个 bundle 中导入 API 包,从另一个中导入实现包。尽管 API 包和实现包已被分离,提供商仍然导出实现包,而不是作为服务公开该类。
图 7. 设计糟糕的提供商 bundle,其中 API 类和实现类已被分离
?
最后一个场景如图 8 所示,显示了最佳实践。提供商 bundle 已经分离了实现包和 API 包,此外,实现类也被作为 OSGi 服务公开。
图 8. 设计良好的提供商 bundle,其中 API 类和实现类已被分离
?
?
?
图 10. 一个设计良好的系统,其中一个 API 的每个实现都由一个独立的 bundle 提供
?
?
?
图 12. 从一个 bundle 中导出的包保持高 bundle 聚合
?
在运行时,OSGi 将用另一个 bundle 的导出满足 Import-Package 头部指定的 bundle 的依赖性。即使这不只一个 bundle 导出包,也只有一个用于满足这种依赖性。Bundles 是 “连接” 在一起的。仅存于其他导出相同包的 bundle 中的类(导入 bundle 的包没有被连接在一起)对于导入 bundle 的类加载器是不可见的。如果导入 bundle 在运行时试图使用那些类其中之一,将抛出一个 ClassNotFoundException。因为在 OSGi 中的导入/导出 bundle 在每个包基础上只提供一种方法将一个 bundle 连接到另一个 bundle,另一个机制则需要将一个 bundle 连接到多个 bundle, 在这种拆分包的情况下工作,这可以通过使用 Require-Bundle 头部和指定输出所需包的 bundle 符号名来完成。
然而,这加强了 bundle 之间的耦合:
客户端现在依赖两个(或更多)bundle 提供包。所有通过提供 bundle 导出的包现在对客户端是可见的,并且当客户端 bundle 开发一段时间以后,它将开始依赖其他包。 提供包的 bundle 不再被另一个包替换,只是使用了一个不同的符号名,对所有需要他的客户端 bundle 没有更改。将包分离成多个 bundle 从而强迫使用 Require-Bundle 头部使应用程序更紧密的耦合,维护和扩展的费用也因此更为昂贵。如果您有一个包可以分成两个 bundle,而您有不需要这两部分有高聚合性,那么您应该使用两个不同的包。
?
然后,部署一个含有 Bundle Y 的应用程序(图 14)。Bundle Y 从 1.5 到 2.0 版本(但不包括 2.0)导入 org.hal.a 包。API.B 也被部署,在版本 1.5 中导出包 org.hal.a。Bundle Y 不能连接到 API.A ,因为它导入一个新的 org.hal.a 版本,但它 可以 被连接到 API.B。Implementation.B bundle 提供了一个 org.hal.a 1.5 版本的实现,从 1.5 到 1.6 版本(但不包括 1.6)导入。OSGi Alliance Semantic Versioning Technical Whitepaper 对这一原因进行详细的讨论。
图 14. OSGi 将 Bundle Y 连接到 API.B 来在可接收 Bundle Y 的版本上提供 org.hal.a
?
?
然后,部署第 3 个应用程序,其中含有 Bundle Z。Bundle Z 从与 Bundle X 相同的版本导入 org.hal.a 包:从版本 1.0 到版本 2.0 但不包括 2.0(图 15)。Bundle Z 的编写者想要它使用 Implementation.A,但是由于 API.B 是在版本 1.5 上导出 org.hal.a 的(这处在 Bundle Z 的导入范围中),Bundle Z 能被连接到 API.B,随后只能看到 Implementation.B。
图 15. Bundle Z 被连接到 API.B,因此不能看到 Implementation.A
?
?
为了确保 Bundle Z 连接到 API.A,Bundle Z 应该指定一个 Use-Bundle 应用程序清单头部:API.A,然后,Bundle Z 将获得对 Implementation.A 的可见性,而不是 Implementation.B。这就是作者想要的(图16)。
图 16. Bundle Z 被连接到 API.A,现在可以看到 Implementation.A
?
12. 使用持久 bundle 来共享您的持久性单元
在应用程序中使用 JPA 时,将 bundle 包装成一个 OSGi 持久性 bundle。如果您的客户不直接使用 EntityManagerFactory,在 OSGi 服务存储库中公开一个数据访问服务,而不是从持久性 bundle 导出实体类。
原因如下
WebSphere Application Server OSGi 应用程序特性支持符合OSGi JPA 服务规范的 OSGi 持久性 bundle。通过在一个持久性 bundle 中包装 JPA 管理的类和 persistence.xml,用易共享的 JPA 资源(在 WebSphere Application Server OSGi 运行时使用的)和不可管理的 JPA Service 实现创建一个可重用模块是有可能的。持久 bundle 也可被大量基于持久性客户端的 OSGi 共享,但不包括持久性客户端代码,这两个都是高度聚合和易于重用的。记得指定 Meta-Persistence bundle 清单头部,持久性描述符可以在包内给出任意路径。
如果一个持久性单元被包装在一个遗留 WAR 中,它应该被打开作为一个单独的 OSGi bundle。在 WAR 中的持久性单元由 Java EE 容器管理,并且在 OSGi 框架内不能共享。同样的,不能被 OSGi 应用程序中的其他 OSGi bundle 所用。从 WAR 中删除持久性单元不是可能的,记住它是不能通过 Blueprint 容器或 OSGi 服务存储库访问的。
从其他应用程序代码中分离持久性 bundle 的另一个原因由 前面的一个最佳实践 来解释。如果几个应用程序需要在一个数据库中访问数据,重用同一个实体映射是有必要的。这有助于防止数据库中的数据损坏,因为两个应用程序共享同一数据映射,而只有一个 bundle 需要针对未来的模式更改而更新。如果持久性单元不能包装在一个较大的 bundle 中,那么实体将不再易于多个应用程序共享,需要代码副本和维护成本。
13. 充分利用提供的组件模型
(专用于 WebSphere Application Server)
在 OSGi、WebSphere Application Server OSGi Applications 功能和 SCA 之间提供一系列不断增长的粗粒度组件模型:
使用 Blueprint 在 OSGi bundle 中定义细粒度组件。将 OSGi 应用程序定义为 bundle 组合,这些 bundle 提供一个特定应用程序功能。 使用 SCA 公开服务,从一个 OSGi 应用程序到另一个,并向常规 Java EE 应用程序提供服务。原因如下
使用 Blueprint 来管理细粒度组件以及与其他 OSGi Bundle 的交互。保持您 bundle 的聚合性(通常很小)并使 您的依赖项保持松耦合性。高聚合和松耦合的原则继续适用,因为 bundle 被分组到业务 bundle 应用程序。
OSGi 应用程序可以认为同传统 Java EE 企业级应用程序大致相同。在组装应用程序时,记住 WebSphere Application Serverkeep 将 Application-Content 列出的 bundle 部署到自己的独立的框架。所有应用程序的依赖 bundle 被部署到一个共享 bundle 空间。因此鼓励将通用代码库和服务分解到共享的依赖 bundle 。这节省了内存,增加了可重用性,并有助于系统的管理。在一个给定 OSGi 应用程序 Application-Content 中列出的 bundle 仅由该应用程序中独有的持久性、业务和显示逻辑组成。
有两个方法可以将工作深入到 OSGi 应用程序,第一个是通过 HTTP 深入到 WAR,第二个是调用一个 Application-ExportService 头部列出的服务;这么做需要使用 SCA。
SCA 是最粗粒度的可用组件模型,它提供一个分布式编程模型并强迫使用值传递语义。使用 SCA 从一个 OSGi 应用程序向另一个公开服务,并在常规 Java EE 程序之间提供服务。Rational Application Developer 8 提供工具支持构建和集成 OSGi 应用程序作为 SAC 组件。
14. 让容器担起重任
(专用于 WebSphere Application Server)
使用容器服务来构建应用程序并提企业品质服务而不是编写代码来管理服务。
原因如下
应用程序程序员经常希望以一种健壮可信的方式解决复杂的问题,并希望信任业务服务,例如事务和托管的资源,来为他们提供他们想要的服务质量。然而,向容器提供这些服务的完全控制权通常有点勉强,这无疑有损灵活性并失去控制。结果是许多应用程序选择管理自己的资源和周期。
控制反转,特别是依赖注入,是极为强大的简化应用程序开发工具,但它们依赖一个事实:容器能负责周期和资源管理。向 Blueprint bean 中的所有方法应用声明事务需要一行 XML,然而在每个方法中需要多行代码来在应用程序中完成同样的结果。类似地,对于资源管理,使用一个 Blueprint 资源引用可以将资源直接注入应用程序,也可以使其可以使用安全证书进行配置,以至于资源没有必要在应用程序中提供。如果一个开发人员选择定位自己的资源,容器就没有机会验证应用程序,因此证书被存储在应用程序中,而且,如果证书过期应用程序必须被更改。
示例
bundle 提供一个基于 JPA 的数据访问服务,但是不使用任何容器服务。它必须手工管理事务、EntityManager 周期和服务存储库。这将需要大约 100 行代码。需要多个 try/finally 块整理资源。客户端必须使用 OSGi API 来查看服务,这需要更多的周期管理和 try/finally 块。由于不使用 Blueprint 注册和使用服务,应用程序也必须手工处理服务不可用的错误案例。这也不是自动基于服务配置的。
通过使用声明性事务、Blueprint 和 管理 JPA,应用程序在大小上有所减少,减少了数百行复杂的周期和错误管理代码。这也减少了应用程序潜在缺陷的数量。这个应用程序,以及将来所有使用数据访问服务的应用程序,都可以利用基于服务的依赖配置优势。
?
?
?
?
?
本文描述了构建 OSGi bundle 和 WebSphere Application Server OSGi 应用程序来实现 bundle 之间的高聚合和松耦合的最佳实践。通过采用这些最佳实践,您可以使用 Feature Pack for OSGi 应用程序和 JPA 2.0 来创建更加可维护和可扩展的应用程序,这些应用程序所用的是运行在 WebSphere Application Server V7 中的技术。
<!-- CMA ID: 525067 --><!-- Site ID: 10 --><!-- XSLT stylesheet used to transform this file: dw-article-6.0-beta.xsl -->
?
学习
WebSphere Application Server Information Center,其中包含 Feature Pack for OSGi Applications and JPA 2.0 文档。 OSGi Alliance Semantic Versioning Technical Whitepaper(PDF) IBM developerWorks WebSphere