古巴:为生产做准备
“它可以在我的本地機器上運行!” 如今,這聽起來像模因,但仍然存在“開發環境與生產環境”的問題。 作為開發人員,您應始終牢記,您的應用程序有一天將在生產環境中開始運行。 在本文中,我們將討論一些特定于CUBA的事情,這些事情將幫助您避免在應用程序投入生產時出現問題。
編碼準則
優先服務
幾乎每個CUBA應用程序都實現一些業務邏輯算法。 此處的最佳實踐是在CUBA Services中實現所有業務邏輯。 所有其他類:屏幕控制器,應用程序偵聽器等應將業務邏輯執行委托給服務。 此方法具有以下優點:
請記住,業務邏輯包括條件,循環等。這意味著理想情況下,服務調用應該是單行的。 例如,假設我們在屏幕控制器中具有以下代碼:
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庫軟件包作為外觀和注銷實現。 您只需要向類代碼注入日志記錄工具,就可以了。
@Inject private Logger log;然后只需在您的代碼中調用此服務:
log.info("Transaction for the customer {} has succeeded at {}", customer, transaction.getDate());請記住,日志消息應該有意義并且包含足夠的信息以了解應用程序中發生了什么。 在系列文章“干凈的代碼,干凈的日志”中,您可以找到更多關于Java應用程序的日志記錄技巧。 另外,我們建議您參閱“ 9個記錄的罪過”一文 。
另外,在CUBA中,我們有性能統計日志,因此您始終可以查看應用程序如何消耗服務器資源。 當客戶支持開始收到用戶對應用程序運行緩慢的投訴時,這將非常有幫助。 通過此登錄,您可以更快地找到瓶頸。
處理異常
異常非常重要,因為異常會在您的應用程序出現問題時提供有價值的信息。 因此,第一條規則-永遠不要忽略例外。 使用log.error()方法,創建有意義的消息,添加上下文和堆棧跟蹤。 該消息將是您用來標識發生了什么的唯一信息。
如果您有代碼約定,請在其中添加錯誤處理規則部分。
讓我們考慮一個示例–將用戶的個人資料圖片上傳到應用程序。 此個人資料圖片將保存到CUBA的文件存儲和文件上傳API服務中。
這是您不得處理異常的方式:
try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (Exception e) {}如果發生錯誤,則沒人會知道,當用戶看不到個人資料照片時,他們會感到驚訝。
這好一些,但遠非理想。
try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (FileStorageException e) {log.error (e.getMessage)}日志中將出現錯誤消息,我們將僅捕獲特定的異常類。 但是將沒有有關上下文的信息:文件的名稱是誰,誰曾嘗試上傳它。 而且,將沒有堆棧跟蹤,因此很難找到異常發生的位置。 還有一件事–用戶不會收到有關該問題的通知。
這可能是一個好方法。
try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (FileStorageException e) {throw new RuntimeException("Error saving file to FileStorage", e);}我們知道錯誤,不要丟失原始異常,添加一條有意義的消息。 調用方法將收到有關異常的通知。 我們可以在消息中添加當前用戶名以及可能的文件名,以添加更多上下文數據。 這是CUBA Web模塊的示例。
在CUBA應用程序中,由于其分布式特性,您可能對核心模塊和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,主機名等作為純字符串存儲在應用程序的代碼中,切勿將其復制粘貼。 在代碼中更改硬編碼值的成本要高得多。 郵件服務器地址,用戶的照片縮略圖大小,沒有網絡連接時的重試次數–所有這些都是您需要外部化的屬性的示例。 使用[配置接口] https://doc.cuba-platform.com/manual-latest/config_interface_usage.html )并將它們注入您的類中以獲取配置值。
利用運行時配置文件將特定于環境的屬性保存在單獨的文件中。
例如,您在應用程序中使用支付網關。 當然,您不應在開發過程中花費大量金錢來測試功能。 因此,您在本地環境中有一個網關存根,在網關端有一個用于生產前測試環境的測試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.properties , test-app.properties和prod-app.properties并在其中定義database.host.name屬性的三個不同值。
之后,定義一個配置接口:
@Source(type = SourceType.DATABASE) public interface PaymentGwConfig extends Config {@Property("payment.gateway.host.name")String getPaymentGwHost(); }然后注入接口并在您的代碼中使用它:
@Inject PaymentGwConfig gwConfig;//service codeconnectToPaymentsGateway(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通過該插件支持多租戶,它對應用程序的數據模型和數據庫的查詢邏輯進行了一些更改。 例如,一個單獨的列tenantId被添加到所有特定于Tenant的實體。 因此,所有查詢都隱式修改為使用此列。 這意味著在編寫本機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框架創建應用程序時,系統中將創建兩個用戶: admin和anonymous 。 始終在生產環境中更改其默認密碼,然后用戶才能使用該應用程序。 您可以手動執行此操作,也可以將SQL語句添加到30-....sql初始化腳本中。
使用CUBA文檔中的建議,這些建議將幫助您正確配置生產中的角色。
如果您具有復雜的組織結構,請考慮為每個分支機構創建本地管理員 ,而不是在組織級別上創建多個“超級管理員”用戶。
將角色導出到生產
在第一次部署之前,通常需要將角色和訪問組從開發(或登臺)服務器復制到生產服務器。 在CUBA中,您可以使用內置的管理UI來執行此操作,而不必手動執行。
要導出角色和特權,您可以使用Administration -> Roles屏幕。 下載文件后,您可以將其上傳到應用程序的生產版本。
對于訪問組,有一個類似的過程,但是您需要使用Administration -> Access Groups屏幕。
配置應用
生產環境通常與開發環境以及應用程序配置不同。 這意味著您需要執行一些其他檢查,以確保您的應用程序在生產時能夠平穩運行。
配置日志
確保已針對生產環境正確配置了日志記錄子系統:日志級別已設置為所需級別(通常為INFO),并且在應用程序重新啟動時不會刪除日志。 您可以參考文檔以獲取正確的日志設置和有用的記錄器參考。
如果使用Docker,請使用Docker卷將日志文件存儲在容器外部。
為了進行正確的日志記錄分析,您可以部署特殊的工具來收集,存儲和分析日志。 例如ELK stack和Graylog 。 建議將日志記錄軟件安裝到單獨的服務器上,以避免對應用程序造成性能影響。
在群集配置中運行
可以將CUBA應用程序配置為在群集配置中運行。 如果決定使用此功能,則需要注意您的應用程序體系結構,否則,您可能會從應用程序中得到意外的行為。 我們希望引起您對專門針對集群環境進行調整的最常用功能的注意:
任務調度
如果要在應用程序中執行預定任務(例如每日報告生成或每周電子郵件發送),則可以使用相應的框架內置功能ъ( https://doc.cuba-platform.com/manual-latest /scheduled_tasks.html )。 但是,請想象自己是一位獲得了三封相同營銷電子郵件的客戶。 你快樂嗎? 如果您的任務在三個群集節點上執行,則可能會發生這種情況。 為避免這種情況,最好使用CUBA任務計劃程序 ,該程序使您可以創建單例任務。
分布式緩存
緩存是可以提高應用程序性能的東西。 有時開發人員嘗試緩存幾乎所有內容,因為現在內存非常便宜。 但是,當您的應用程序部署在多臺服務器上時,緩存將在服務器之間分配,并且應該同步。 同步過程發生在相對較慢的網絡連接上,這可能會增加響應時間。 這里的建議–在決定添加更多緩存之前(尤其是在集群環境中),執行負載測試并衡量性能。
結論
CUBA平臺簡化了開發,您可能會完成開發并開始考慮比預期更早的投入生產。 但是,無論是否使用CUBA,部署都不是一件容易的事。 而且,如果您開始考慮在開發的早期階段就進行部署并遵循本文所述的簡單規則,那么您的生產方式很可能會很順利,所需的工作量很小,并且不會遇到嚴重的問題。
翻譯自: https://www.javacodegeeks.com/2020/03/cuba-getting-ready-for-production.html
總結
- 上一篇: 怎么清理电脑机箱(怎么清理电脑机箱内的灰
- 下一篇: 华为p50pro上市时间介绍(华为p50