目录

  • CUBA:如何准备上线
    • 编码准则
      • 使用服务(Services)
      • 使用无状态
      • 使用日志
      • 异常处理
    • 环境相关的配置
      • 使用恰当的服务实现
      • 将配置参数外部化
      • 添加网络超时处理
    • 数据库准则
      • 生成特定环境的数据库脚本
      • 考虑多租户
      • 安全性考虑
      • 安全的编码
        • 准则 3-2 / INJECT-2: 避免动态 SQL
        • 准则 5-1 / INPUT-1: 验证输入
      • 保护个人数据安全
      • 修改或者禁用默认用户和角色
      • 导出角色至生产环境
    • 配置应用程序
      • 日志配置
      • 在群集配置中运行
        • 计划任务
        • 分布式缓存
    • 结论

CUBA:如何准备上线

“在我电脑上是好的呢!”现在看来,这句话更像是调侃开发人员的一个段子,但是“开发环境与生产环境”之间的矛盾依然存在。作为开发者,你需要记住,你写的应用总会有在生产环境上线的一天。在本文中,我们将讨论一些 CUBA 的特性,能帮助你避免在应用程序上线时遇到问题。

编码准则

使用服务(Services)

几乎每一个 CUBA 应用程序都会实现一些业务逻辑算法。实现业务逻辑的最佳实践就是所有的业务代码都在 CUBA 服务(CUBA Services)中实现。所有其他的类,包括界面控制器、应用程序监听器等等,应当将业务逻辑全部交由服务来代理执行。这个方式的好处是:

  1. 对于一个业务逻辑来说,只在一处实现。
  2. 可以在不同的地方调用此业务逻辑,并能将其作为开放的 REST 服务。

请注意,业务逻辑中一般会包括条件、循环等。在理想情况下,对服务的调用应该是单线的。比如,我们假设在控制器中有如下代码:

Item item = itemService.findItem(itemDate);
if (item.isOld()) {itemService.doPlanA(item);
} else {itemService.doPlanB(item);
}

如果你看到这种代码,可以考虑在 itemService 服务中创建一个单独的方法 processOldItem(Date date) ,将这段分支判断逻辑迁移到服务中实现,因为这段代码看上去更像是业务逻辑的一部分。

由于界面和 API 可能会是不同的团队开发,所以保持业务逻辑只存在一个地方可以避免生产环境中应用程序行为的不一致。

使用无状态

当开发 web 应用程序时,需要时刻记得将会有多个用户同时使用你的程序。在代码中,意味着有些代码会被多个线程同时执行。几乎所有的应用程序组件,比如服务、bean 以及事件监听器都会受到多线程执行的影响。这里的最佳实践就是让组件保持无状态。 也就是说不要引入共享的可变类成员。 使用局部变量并将会话级特定的信息保留在用户之间不共享的应用程序存储中。 例如,可以在用户会话中保留少量可序列化的数据。

如果需要共享更多的数据,可以使用数据库或者专门的内存共享存储,比如 Redis。

使用日志

有时,生产环境会出一些问题。当问题产生时,很难搞清楚到底是什么引起的问题,因为没办法在生产环境调试。所以为了使你自己、团队以及支撑团队将来的工作更加轻松,为了能明白问题怎么发生的,以便能重现,最好在应用程序中添加适量的日志。

此外,日志还扮演了被动监测的角色。在应用程序重启、升级或者重新配置之后,管理员通常通过查看日志确保所有的程序都启动成功。

日志还能帮助解决可能不是在你的应用程序中发生的问题,而是应用程序集成的其他服务中。 例如,要弄清楚为什么支付网关会拒绝某些交易,可能需要记录所有数据,然后在与支撑团队沟通时提供这些数据。

CUBA 使用了经过验证的 slf4j 库作为日志切面和 logback 实现。 只需要向类代码中注入日志工具类,就可以用了:

@Inject
private Logger log;

然后直接调用:

log.info("Transaction for the customer {} has succeeded at {}", customer, transaction.getDate());

请记住,日志消息需要有意义并包含足够的信息以便了解在应用程序中到底发生了什么。

还有,CUBA 自带性能统计日志,所以你可以看到应用程序对服务器资源的消耗情况。当收到用户抱怨应用程序很慢时,这些日志非常有用,用它们能更快的找到性能瓶颈。

异常处理

异常是非常重要的,在应用程序发生错误时,它们能提供非常有价值的信息。因此,第一条规则就是在代码中永远不要忽视任何异常。使用 log.error() 方法创建一个有含义的消息,添加上下文和 stack trace。这个消息是你能用来定位错误的唯一信息。

如果你有编码规范,那么添加一章关于错误处理的章节吧。

我们看一个例子 - 上传一张用户的资料照片至应用程序。这个资料照片会使用上传 API 服务并存储在 CUBA 的文件存储中。

如果不处理异常,代码是这样:

try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd);
} catch (Exception e) {}

如果发生了错误,鬼才会知道。用户会很奇怪,我的照片怎么上不去?
下面这个好一些,但是还不理想:

try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd);
} catch (FileStorageException e) {log.error (e.getMessage)
}

日志中会保存一条错误信息,但是我们只捕获了特定的异常类。也没有提供有关上下文的信息:文件的名称是什么,谁曾尝试上传文件。 而且,由于没有 stack trace 的信息,因此很难找到异常发生的位置。 还有,用户根本不会知道这里发生了错误。

下面这个应该是一个比较好的方式:

try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (FileStorageException e) {throw new RuntimeException("Error saving file to FileStorage", e);
}

我们知道错误是什么,也没有丢失原始异常,添加了一条有意义的消息文本。 调用方法将收到有关异常的通知。 我们可以将当前用户名以及上传文件名添加到消息中,以增加更多上下文数据。 这是 CUBA Web 模块的示例。

在 CUBA 应用程序中,由于其分布式特性,你可能在 Core 模块和 Web 模块具有不同的异常处理规则。 文档中有一个关于异常处理的特殊部分。 在实现异常处理时,建议先阅读该章节。

环境相关的配置

开发应用程序时,请试试将应用中与环境相关的部分独立出来,然后试用功能开关和配置文件根据环境切换这些部分的功能。

使用恰当的服务实现

CUBA 中的任何服务都包含两个部分:接口(服务 API)及其实现。有时候,服务的实现依赖于部署环境。我们拿文件存储服务作为例子。

在 CUBA 中,可以使用文件存储来保存发送至应用程序的文件,然后可以在服务中使用这些文件。其默认实现是使用服务器上的本地文件系统保存文件。

但是,当你部署应用程序至生产服务器时,如果使用了云环境或者集群部署,这个实现就不一定能正常工作了。

如果要使用针对环境的不同服务实现,CUBA 支持运行时配置,可以根据启动参数或者环境变量使用特定的服务实现。

对于上面这个例子,如果我们想用 Amazon S3 作为生产环境文件存储的实现,可以用下面的方式指定这个 bean:

<beans profile="prod"><bean name="cuba_FileStorage" class="com.haulmont.addon.cubaaws.s3.AmazonS3FileStorage"/></beans>

然后,在设置了下面这个属性时,会自动启用基于 S3 的实现:

spring.profiles.active=prod

因此,当开发 CUBA 应用程序时,尝试将环境相关的服务先定义出来,然后为每种环境启用适当的服务实现。不要写下面这种代码:

If (“prod”.equals(getEnvironment())) {executeMethodA();
} else {executeMethodB();
}

此时,需要实现一个单独服务 myService 带有一个方法 executeMethod() 和两个实现类,然后通过配置文件来配置。这样在调用此服务的地方,只有:

myService.executeMethod();

干净、简单、易维护。

将配置参数外部化

如果可能的话,将应用程序的配置提取到属性文件中。 如果参数将来可以修改(即使概率很小),也需要将其外部化。代码中应尽量避免将连接URL、主机名等作为纯字符串,并且千万不要到处复制粘贴。 在代码中更改硬编码(hardcode)值的成本要高得多。 邮件服务器地址、用户的照片缩略图大小、没有网络连接时的重试次数,等等所有这类配置都是需要外部化的属性。 使用配置接口,并在你的类中注入即可获取配置值。

使用运行时配置将环境相关的属性放在单独的文件中。

还是拿支付网关举个例子。 首先,我们不会在开发过程中使用真正的钞票来测试功能。 因此,会有一个用于本地环境的网关存根,一个在网关端用于预发布测试环境的测试 API 和一个用于生产环境的真实网关。 显然,这些环境的网关地址不同。

别这么写代码:

If (“prod”.equals(getEnvironment())) {gatewayHost = “gateway.payments.com”;
} else if (“test”.equals(getEnvironment())) {gatewayHost = “testgw.payments.com”;
} else {gatewayHost = “localhost”;
}
connectToPaymentsGateway(gatewayHost);

而是,定义三个属性文件:dev-app.propertiestest-app.properties 以及 prod-app.properties,分别在其中定义 payment.gateway.host.name 的值。之后,在项目中添加一个配置接口:

@Source(type = SourceType.DATABASE)
public interface PaymentGwConfig extends Config {@Property("payment.gateway.host.name")String getPaymentGwHost();
}

然后可以在代码中注入该接口并使用:

@Inject
PaymentGwConfig gwConfig;// 其它代码connectToPaymentsGateway(gwConfig.getPaymentGwHost());

这样的话,代码会很简单,所有的设置都在属性文件中,如果环境有变,也不需要在代码中查找了。

添加网络超时处理

开发时,要始终认为通过网络进行的服务调用不可靠。当前用于 Web 服务调用的大多数第三方库都基于同步阻塞通信模型。也就是说,如果从主执行线程调用 Web 服务,则应用程序将暂停直到收到响应为止。

即使在单独的线程中执行 Web 服务调用,由于网络超时,该线程也有可能永远无法恢复执行。

有两种类型的超时:连接超时和数据读取超时。

在应用程序中,这些超时类型应分开处理。我们还是看看支付网关的例子。此时,读取超时可能明显长于连接超时。银行交易可以处理很长的时间,数十秒,最多几分钟。但是连接应该很快,那么这里我们可以将连接超时设置为一个稍长的时间,比如10秒。

超时时限的配置可以移至属性文件,这基本上是一个最典型的例子了。为需要通过网络交互的所有服务设置该值。以下是服务bean定义的示例:

<bean id="paymentGwConfig" class="com.global.api.serviceConfigs.GatewayConfig"><property name="connectionTimeout" value="${xxx.connectionTimeoutMillis}"/><property name="readTimeout" value="${xxx.readTimeoutMillis}"/></bean>

在你的代码中,需要包含一段专门用来处理超时的代码。

数据库准则

数据库是几乎所有应用程序的核心。 在部署和更新生产环境时,不破坏数据库是非常重要的。 除此之外,工作负载方面,开发人员工作站上的数据库显然与生产服务器不同。 因此,可能要实施以下描述的一些做法。

生成特定环境的数据库脚本

CUBA 中,我们能生成创建和更新应用程序数据库的 SQL 脚本。在生产环境第一次数据库创建之后,只要数据模型发生改动,CUBA 框架都会生成更新脚本。

文档中有一个章节专门介绍生产环境的数据库更新。请在第一次部署生产环境之前仔细阅读。

最终建议,在更新数据库之前先做备份。这样做能节省很多时间,并且也不用担心出问题。

考虑多租户

如果你的项目是一个多租户项目,请在项目开始时就将多租户的场景设计好。

CUBA 通过扩展插件支持多租户,该扩展会引入一些应用程序数据模型以及数据库查询语句逻辑的改动。比方说,对于租户场景下的实体,会添加 tanantId 这一列。因此,所有查询这些实体的查询语句都被隐式的做了修改,以便能使用该列作为查询条件。也就是说,如果你需要写自定义 SQL 查询,需要考虑此列的存在。

请注意,由于上面提到的特定功能,向在生产环境中运行的应用程序添加多租户功能可能很棘手。 为了简化迁移,请将所有自定义查询保留在同一应用程序层中,最好保留在服务中或单独的数据访问层中。

安全性考虑

对于可以被多个用户访问的应用程序,安全性起着重要的作用。 为避免数据泄漏、未经授权的访问等,需要认真考虑安全性。下面会介绍一些原则,这些原则可以帮助改善系统的安全性。

安全的编码

提到安全性,首先就要写出能阻止问题产生的代码。 可以在此处找到Oracle提供的有关安全编码的参考。 下面我们介绍其中的一些(也许很明显的)建议。

准则 3-2 / INJECT-2: 避免动态 SQL

大家都知道,动态创建的 SQL 语句(带有不受信任的输入)容易受到命令注入的攻击。 在 CUBA 中,你很可能需要执行 JPQL 语句,因此,也请避免使用动态 JPQL。 如果需要添加参数,请使用适当的类和语句语法:

try (Transaction tx = persistence.createTransaction()) {// get EntityManager for the current transactionEntityManager em = persistence.getEntityManager();// create and execute QueryQuery query = em.createQuery("select sum(o.amount) from sample_Order o where o.customer.id = :customerId");query.setParameter("customerId", customerId);result = (BigDecimal) query.getFirstResult();// commit transactiontx.commit();
}

准则 5-1 / INPUT-1: 验证输入

在使用前,必须验证来自不受信任来源的输入。 恶意的输入可能会导致问题,无论是通过方法参数还是外部流。 比如整数值溢出和通过在文件名中包含“ …/”序列的目录遍历攻击。 在CUBA中,除了在代码中检查之外,还可以在 GUI 中使用验证器。

以上只是安全编码原理的几个简单例子。 请仔细阅读该指南,能在多方面改善你的代码安全性。

保护个人数据安全

由于法律要求,某些个人信息应受到保护。 在欧洲,有通用数据保护条例(GDPR),在美国的医疗应用中,有健康保险携带和责任法案(HIPAA)要求等,中国近年来对个人信息的保护也上升到了一个新的高度,不少公司因为泄露个人隐私的问题而被调查。因此,在实施项目时要考虑到这一点。

使用 CUBA 可以设置各种权限,并使用角色和访问组限制对数据的访问。 在访问组中,还可以定义各种约束,以防止未经授权访问个人数据。

但是访问权限控制只是确保个人数据安全的一部分。 数据保护标准和行业特定要求还有很多。 在规划应用程序的体系结构和数据模型之前,需要先了解这些要求。

修改或者禁用默认用户和角色

当使用 CUBA 框架创建应用程序时,系统会创建两个默认用户:adminanonymous。最好在系统上到生产环境之后,客户开始用之前修改这两个用户的默认密码。可以手动修改或者在 30-....sql 的初始化脚本中添加 SQL 语句修改。

按照 CUBA 文档的推荐步骤可以很好的配置生产环境需要使用的角色。

如果应用程序需要支持复杂的组织结构,可以考虑在组织机构级别为每个分支机构创建本地管理员,而不是创建多个“超级管理员”。

导出角色至生产环境

在第一次部署之前,通常需要从开发环境或预发布(staging) 环境将角色和访问组复制到生产服务器。 在CUBA中,可以使用内置的管理界面来执行此操作,而不必手动从数据库复制。

导出角色和权限配置可以使用 Administration->Roles 界面内的导出按钮。下载导出的文件之后,可以在生产环境使用该界面进行导入。访问组也有类似的按钮操作。

配置应用程序

生产环境通常与开发环境不同,应用程序配置也会不同。也就是说,需要进行一些其他检查,以确保应用程序在生产时能够平稳运行。

日志配置

确保已针对生产环境正确配置了日志子系统:日志级别已设置为所需级别(通常为 INFO),并且在应用程序重启时不会删除日志。 可以参考文档了解正确的日志设置和有用的 Logger。

如果使用 Docker,请使用 Docker volumes 将日志文件存储在容器外部。

为了进行正确的日志记录分析,可以部署特殊的工具来收集、存储和分析日志。 比如 ELK 家族或者 Graylog。 建议将日志记录软件安装到单独的服务器上,以避免对应用程序造成性能影响。

在群集配置中运行

可以将CUBA应用程序配置为在集群中运行。 如果决定使用此功能,则需要注意你的应用程序架构,否则,可能会有不符合期望的行为出现。 下面几点针对群集环境需要专门调整的最常用功能:

计划任务

如果要在应用程序中执行计划任务,例如每天生成报告或每周发送电子邮件,则可以使用相应的框架内置功能。 但是,请想象一下,如果你是一位同时收到了三封相同营销电子邮件的客户。你会觉得开心吗?如果计划任务在三个群集节点上都执行,则很可能会发生这种情况。 为避免这种情况,最好使用 CUBA 任务计划程序,该程序可以创建单例(singleton)任务。

分布式缓存

缓存是可以提高应用程序性能的东西。有时开发人员会尝试把几乎所有的数据都进行缓存,因为现在内存非常便宜。但是,当应用程序使用集群部署在多台服务器上时,将在服务器之间分配缓存,还会做缓存同步。如果同步过程发生在相对较慢的网络连接上,这可能会增加响应时间。所以在决定添加更多缓存之前(尤其是在集群环境中),需要先进行负载测试并衡量性能。

结论

CUBA 平台简化了开发过程,所以很可能你会提前完成开发并开始考虑投入生产。可是,无论是否使用 CUBA,部署都不简单。如果能在开发的早期阶段就开始思考部署流程并遵循本文所述的简单规则,那么你的应用程序投入生产的过程应该会很顺利,所需的工作量很小,并且不会遇到严重的问题。

CUBA:如何准备上线相关推荐

  1. SpringBoot服务上线流程

    前言: 最近因为培养计划调用师徒制的接口,就把师徒制整个后端上线了,然后实现服务间调用!整理整个上线过程,很简洁的步骤!

  2. unigui中弹出对话框原窗体是没有了_最前线 | 微信对话框“搜一搜”功能上线,独辟蹊径的腾讯打着什么算盘?...

    更新界的"劳模"微信又出新花样了.9月9日,微信在对话框全量上线了搜一搜功能.简单来说,就是用户在微信对话过程中,如果遇到知识盲区,可以通过长按对话框文本,选择导航栏中的" ...

  3. 又拍云SSL证书全新上线,提供一站式HTTPS安全解决方案

    互联网快速发展,云服务早已融入每一个人的日常生活,而互联网安全与互联网的发展息息相关,这其中涉及到信息的保密性.完整性.可用性.真实性和可控性.又拍云上线了与多家国际顶级 CA 机构合作的数款OV & ...

  4. 微软在.NET官网上线.NET 架构指南频道

    微软在Visual Studio 2017 正式发布的时候也上线了一个参考应用https://github.com/dotnet/eShopOnContainers , 最近微软给这个参考应用写了完善 ...

  5. 全球UML模型共享设计下载中心--www.euml.org上线

    全球UML模型共享设计&下载中心euml.org上线  euml.org是楚凡科技版权所有的公益站点,为全球UML用户和爱好者提供如下服务:  1.在线UML建模工具Trufun eUML.T ...

  6. 必须进行支持的游戏方可使用此功能_企业微信 3.0上线,开放朋友圈功能,又一风口?...

    大家好我是辰由,微信作为一款国民级APP坐拥十几亿活跃用户,工作生活中必不可少,几乎每天都能够用得到,每一次的更新都能够牵动数十亿人的关注. 2019年12月23日企业微信2019年度发布会在广州举行 ...

  7. 情人节 html5,情人节H5案例 | 2019第一波情人节营销已上线

    原标题:情人节H5案例 | 2019第一波情人节营销已上线 刚从春节催婚的泥潭中爬出来,还没好好喘口气,转眼就被2月14日的情人节盯上了-对于压力超大的年轻人,这节是过呢还是不过呢?事实上,无论你有没 ...

  8. adg oracle 架构_云化双活的架构演进,宁夏银行新核心搭载Oracle 19c投产上线

    云和恩墨顺利完成宁夏银行新数据中心数据库平台的建设,包括新数据中心RAC搭建.DG搭建.旧数据中心到新数据中心的数据迁移,以及在整个项目生命周期中的实施规范.性能测试保障.压力测试等.6月12日,宁夏 ...

  9. Python培训班线上线下哪种靠谱

    Python近几年在人工智能领域的快速发展,引起了很多人的注意,各种Python培训机构也越来越多,很多零基础的同学都想通过报培训班学习,目前互联网的发达,Python培训分为线上和线下,那么Pyth ...

  10. APP不同上线情况对应的测试流程

    一个App软件从研发提测到版本上线都会经过哪些测试流程呢?很多人认为就是进行功能测试,没bug了就提交审核,审核通过就直接上线了,其实不然,有些步骤是需要特别关注的,否则极易造成线上bug,本文千锋教 ...

最新文章

  1. 太赞了!机器学习基础核心算法:贝叶斯分类!(附西瓜书案例及代码实现)
  2. Html5 学习系列(一)认识HTML5
  3. Linux7静默安装Oracle11g教程,亲测实用有效!
  4. 【PostgreSQL保存】java.io.IOException: Tried to send an out-of-range integer as a 2-byte value 问题分析+解决方法
  5. 常用MySQ调优策略及相关分享:学习随记
  6. Sqlserver2008相关配置问题
  7. 作业帮:字符串反转(头部插入)
  8. 风险意识培训教程(续)
  9. 欢迎来到嬴政有条北冥的鱼的博客
  10. Excel——字符串操作函数
  11. 数据仓库入门(实验9)查询多维数据集
  12. 【机器学习系列】变分推断第三讲:基于随机梯度上升法SGD的变分推断解法
  13. 机器学习7个主要领域
  14. ngx-datatable中文教程
  15. 高并发访问数据库问题
  16. jenkins部署微服务项目
  17. List的remove()方法避坑
  18. windows11文件夹修改图标/颜色/样式教程
  19. 文末送书|用Pandas分析了75w多条数据,揭秘美国选民的总统喜好!
  20. 动态网页Reptile

热门文章

  1. 利用华为手机给台式机提供网络
  2. java服务器限速下载_Java文件下载限速
  3. 计算机工程科学计算与仿真,BGPLUS实地科研 |中科院|计算机科学、计算机工程:计算机算法与数值建模实训...
  4. SpringBoot -- 抱团学习社区系统项目实战
  5. linux编译n2n v2,重新编译N2N
  6. CSDN日报191021:我与CSDN的这十年——笔耕不辍,青春热血
  7. Python自然语言处理 3 处理原始文本
  8. 百度ORC识别身份证,JXL导出信息到excel流水作业。
  9. Android 修改AlertDialog原生setPositiveButton的字体颜色背景颜色大小边距位置
  10. 如何使用串口调试助手(调试串口)