古巴:为生产做准备
“它可以在我的本地機(jī)器上運(yùn)行!” 如今,這聽起來像模因,但仍然存在“開發(fā)環(huán)境與生產(chǎn)環(huán)境”的問題。 作為開發(fā)人員,您應(yīng)始終牢記,您的應(yīng)用程序有一天將在生產(chǎn)環(huán)境中開始運(yùn)行。 在本文中,我們將討論一些特定于CUBA的事情,這些事情將幫助您避免在應(yīng)用程序投入生產(chǎn)時(shí)出現(xiàn)問題。
編碼準(zhǔn)則
優(yōu)先服務(wù)
幾乎每個(gè)CUBA應(yīng)用程序都實(shí)現(xiàn)一些業(yè)務(wù)邏輯算法。 此處的最佳實(shí)踐是在CUBA Services中實(shí)現(xiàn)所有業(yè)務(wù)邏輯。 所有其他類:屏幕控制器,應(yīng)用程序偵聽器等應(yīng)將業(yè)務(wù)邏輯執(zhí)行委托給服務(wù)。 此方法具有以下優(yōu)點(diǎn):
請記住,業(yè)務(wù)邏輯包括條件,循環(huán)等。這意味著理想情況下,服務(wù)調(diào)用應(yīng)該是單行的。 例如,假設(shè)我們在屏幕控制器中具有以下代碼:
Item item = itemService.findItem(itemDate); if (item.isOld()) {itemService.doPlanA(item); } else {itemService.doPlanB(item); }如果您看到這樣的代碼,請考慮將其從屏幕控制器移至itemService作為單獨(dú)的方法processOldItem(Date date)因?yàn)樗雌饋硐袷菓?yīng)用程序業(yè)務(wù)邏輯的一部分。
由于屏幕和API可以由不同的團(tuán)隊(duì)開發(fā),因此將業(yè)務(wù)邏輯放在一個(gè)地方可以幫助您避免生產(chǎn)中應(yīng)用程序行為的不一致。
無國籍
開發(fā)Web應(yīng)用程序時(shí),請記住它將被多個(gè)用戶使用。 在代碼中,這意味著某些代碼可以由多個(gè)線程同時(shí)執(zhí)行。 幾乎所有應(yīng)用程序組件:服務(wù),Bean以及事件偵聽器都受多線程執(zhí)行的影響。 此處的最佳做法是使組件保持無狀態(tài)。 這意味著您不應(yīng)引入共享的可變類成員。 使用局部變量并將特定于會話的信息保留在應(yīng)用程序存儲中,用戶之間不共享這些信息。 例如,您可以在用戶會話中保留少量可序列化的數(shù)據(jù)。
如果需要共享一些數(shù)據(jù),請使用數(shù)據(jù)庫或?qū)S玫墓蚕韮?nèi)存存儲(例如Redis)。
使用記錄
有時(shí)生產(chǎn)中會出問題。 而且,當(dāng)發(fā)生這種情況時(shí),很難弄清到底是什么導(dǎo)致了故障,您無法調(diào)試部署到生產(chǎn)的應(yīng)用程序。 為了簡化您自己的工作,開發(fā)人員和支持團(tuán)隊(duì)的同伴并幫助您理解問題并能夠重現(xiàn)此問題,請始終將日志記錄添加到應(yīng)用程序中。
此外,日志記錄還充當(dāng)被動監(jiān)視角色。 應(yīng)用程序重新啟動,更新或重新配置后,管理員通常會查看日志以確保一切都已成功啟動。
日志記錄可能有助于解決可能不是在您的應(yīng)用程序中發(fā)生的問題,而是在與應(yīng)用程序集成的服務(wù)中發(fā)生的問題的解決方法。 例如,要弄清楚為什么付款網(wǎng)關(guān)拒絕某些交易,您可能需要記錄所有數(shù)據(jù),然后在與支持團(tuán)隊(duì)進(jìn)行對話時(shí)使用它們。
CUBA使用了經(jīng)過驗(yàn)證的slf4j庫軟件包作為外觀和注銷實(shí)現(xiàn)。 您只需要向類代碼注入日志記錄工具,就可以了。
@Inject private Logger log;然后只需在您的代碼中調(diào)用此服務(wù):
log.info("Transaction for the customer {} has succeeded at {}", customer, transaction.getDate());請記住,日志消息應(yīng)該有意義并且包含足夠的信息以了解應(yīng)用程序中發(fā)生了什么。 在系列文章“干凈的代碼,干凈的日志”中,您可以找到更多關(guān)于Java應(yīng)用程序的日志記錄技巧。 另外,我們建議您參閱“ 9個(gè)記錄的罪過”一文 。
另外,在CUBA中,我們有性能統(tǒng)計(jì)日志,因此您始終可以查看應(yīng)用程序如何消耗服務(wù)器資源。 當(dāng)客戶支持開始收到用戶對應(yīng)用程序運(yùn)行緩慢的投訴時(shí),這將非常有幫助。 通過此登錄,您可以更快地找到瓶頸。
處理異常
異常非常重要,因?yàn)楫惓谀膽?yīng)用程序出現(xiàn)問題時(shí)提供有價(jià)值的信息。 因此,第一條規(guī)則-永遠(yuǎn)不要忽略例外。 使用log.error()方法,創(chuàng)建有意義的消息,添加上下文和堆棧跟蹤。 該消息將是您用來標(biāo)識發(fā)生了什么的唯一信息。
如果您有代碼約定,請?jiān)谄渲刑砑渝e(cuò)誤處理規(guī)則部分。
讓我們考慮一個(gè)示例–將用戶的個(gè)人資料圖片上傳到應(yīng)用程序。 此個(gè)人資料圖片將保存到CUBA的文件存儲和文件上傳API服務(wù)中。
這是您不得處理異常的方式:
try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (Exception e) {}如果發(fā)生錯(cuò)誤,則沒人會知道,當(dāng)用戶看不到個(gè)人資料照片時(shí),他們會感到驚訝。
這好一些,但遠(yuǎn)非理想。
try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (FileStorageException e) {log.error (e.getMessage)}日志中將出現(xiàn)錯(cuò)誤消息,我們將僅捕獲特定的異常類。 但是將沒有有關(guān)上下文的信息:文件的名稱是誰,誰曾嘗試上傳它。 而且,將沒有堆棧跟蹤,因此很難找到異常發(fā)生的位置。 還有一件事–用戶不會收到有關(guān)該問題的通知。
這可能是一個(gè)好方法。
try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (FileStorageException e) {throw new RuntimeException("Error saving file to FileStorage", e);}我們知道錯(cuò)誤,不要丟失原始異常,添加一條有意義的消息。 調(diào)用方法將收到有關(guān)異常的通知。 我們可以在消息中添加當(dāng)前用戶名以及可能的文件名,以添加更多上下文數(shù)據(jù)。 這是CUBA Web模塊的示例。
在CUBA應(yīng)用程序中,由于其分布式特性,您可能對核心模塊和Web模塊具有不同的異常處理規(guī)則。 文檔中有一個(gè)關(guān)于異常處理的特殊部分。 實(shí)施該政策之前,請先閱讀它。
特定于環(huán)境的配置
開發(fā)應(yīng)用程序時(shí),請嘗試隔離應(yīng)用程序代碼中特定于環(huán)境的部分,然后使用功能切換和配置文件根據(jù)環(huán)境切換這些部分。
使用適當(dāng)?shù)姆?wù)實(shí)施
CUBA中的任何服務(wù)都由兩部分組成:接口(服務(wù)API)及其實(shí)現(xiàn)。 有時(shí),實(shí)現(xiàn)可能取決于部署環(huán)境。 例如,我們將使用文件存儲服務(wù)。
在CUBA中,您可以使用文件存儲來保存已發(fā)送到應(yīng)用程序的文件,然后在服務(wù)中使用它們。 默認(rèn)實(shí)現(xiàn)使用服務(wù)器上的本地文件系統(tǒng)保留文件。
但是,當(dāng)您將應(yīng)用程序部署到生產(chǎn)服務(wù)器時(shí),此實(shí)現(xiàn)可能不適用于云環(huán)境或集群部署配置 。
為了啟用特定于環(huán)境的服務(wù)實(shí)現(xiàn),CUBA支持運(yùn)行時(shí)配置文件 ,該配置文件允許您根據(jù)啟動參數(shù)或環(huán)境變量來使用特定服務(wù)。
對于這種情況,如果我們決定在生產(chǎn)中使用文件存儲的Amazon S3實(shí)現(xiàn),則可以通過以下方式指定bean:
<beans profile="prod"><bean name="cuba_FileStorage" class="com.haulmont.addon.cubaaws.s3.AmazonS3FileStorage"/> </beans>設(shè)置該屬性后,將自動啟用S3實(shí)現(xiàn):
spring.profiles.active=prod因此,在開發(fā)CUBA應(yīng)用程序時(shí),請嘗試識別特定于環(huán)境的服務(wù),并為每種環(huán)境啟用正確的實(shí)現(xiàn)。 盡量不要編寫看起來像這樣的代碼:
If (“prod”.equals(getEnvironment())) {executeMethodA(); } else {executeMethodB(); }嘗試實(shí)現(xiàn)一個(gè)單獨(dú)的服務(wù)myService ,該服務(wù)具有一個(gè)方法executeMethod()和兩個(gè)實(shí)現(xiàn),然后使用配置文件對其進(jìn)行配置。 之后,您的代碼將如下所示:
myService.executeMethod();更清潔,更簡單,更易于維護(hù)。
外部化設(shè)置
如果可能,將應(yīng)用程序設(shè)置提取到屬性文件中。 如果參數(shù)將來可以更改(即使概率很小),請始終對其進(jìn)行外部化。 避免將連接URL,主機(jī)名等作為純字符串存儲在應(yīng)用程序的代碼中,切勿將其復(fù)制粘貼。 在代碼中更改硬編碼值的成本要高得多。 郵件服務(wù)器地址,用戶的照片縮略圖大小,沒有網(wǎng)絡(luò)連接時(shí)的重試次數(shù)–所有這些都是您需要外部化的屬性的示例。 使用[配置接口] https://doc.cuba-platform.com/manual-latest/config_interface_usage.html )并將它們注入您的類中以獲取配置值。
利用運(yùn)行時(shí)配置文件將特定于環(huán)境的屬性保存在單獨(dú)的文件中。
例如,您在應(yīng)用程序中使用支付網(wǎng)關(guān)。 當(dāng)然,您不應(yīng)在開發(fā)過程中花費(fèi)大量金錢來測試功能。 因此,您在本地環(huán)境中有一個(gè)網(wǎng)關(guān)存根,在網(wǎng)關(guān)端有一個(gè)用于生產(chǎn)前測試環(huán)境的測試API,一個(gè)為產(chǎn)品提供了真實(shí)的網(wǎng)關(guān)。 顯然,這些環(huán)境的網(wǎng)關(guān)地址不同。
不要這樣寫代碼:
If (“prod”.equals(getEnvironment())) {gatewayHost = “gateway.payments.com”; } else if (“test”.equals(getEnvironment())) {gatewayHost = “testgw.payments.com”; } else {gatewayHost = “l(fā)ocalhost”; } connectToPaymentsGateway(gatewayHost);而是定義三個(gè)屬性文件: dev-app.properties , test-app.properties和prod-app.properties并在其中定義database.host.name屬性的三個(gè)不同值。
之后,定義一個(gè)配置接口:
@Source(type = SourceType.DATABASE) public interface PaymentGwConfig extends Config {@Property("payment.gateway.host.name")String getPaymentGwHost(); }然后注入接口并在您的代碼中使用它:
@Inject PaymentGwConfig gwConfig;//service codeconnectToPaymentsGateway(gwConfig.getPaymentGwHost());該代碼更簡單,并且不依賴于環(huán)境,所有設(shè)置都在屬性文件中,如果更改了某些內(nèi)容,則不應(yīng)在代碼中搜索它們。
添加網(wǎng)絡(luò)超時(shí)處理
始終認(rèn)為通過網(wǎng)絡(luò)進(jìn)行的服務(wù)調(diào)用不可靠。 當(dāng)前用于Web服務(wù)調(diào)用的大多數(shù)庫都基于同步阻塞通信模型。 這意味著,如果您從主執(zhí)行線程調(diào)用Web服務(wù),則應(yīng)用程序?qū)和V钡绞盏巾憫?yīng)。
即使您在單獨(dú)的線程中執(zhí)行Web服務(wù)調(diào)用,該線程也有可能由于網(wǎng)絡(luò)超時(shí)而永遠(yuǎn)無法恢復(fù)執(zhí)行。
超時(shí)有兩種類型:
在應(yīng)用程序中,這些超時(shí)類型應(yīng)分開處理。 讓我們使用與上一章相同的示例-付款網(wǎng)關(guān)。 對于這種情況,讀取超時(shí)可能明顯長于連接超時(shí)。 銀行交易可以處理很長的時(shí)間,數(shù)十秒,最多幾分鐘。 但是連接應(yīng)該很快,因此,值得將連接超時(shí)設(shè)置為例如10秒。
超時(shí)值是要移至屬性文件的良好候選者。 并始終為通過網(wǎng)絡(luò)交互的所有服務(wù)設(shè)置它們。 以下是服務(wù)bean定義的示例:
<bean id="paymentGwConfig" class="com.global.api.serviceConfigs.GatewayConfig"><property name="connectionTimeout" value="${xxx.connectionTimeoutMillis}"/><property name="readTimeout" value="${xxx.readTimeoutMillis}"/> </bean>在您的代碼中,您應(yīng)該包括一個(gè)特殊部分來處理超時(shí)。
數(shù)據(jù)庫準(zhǔn)則
數(shù)據(jù)庫是幾乎所有應(yīng)用程序的核心。 在生產(chǎn)部署和更新方面,不破壞數(shù)據(jù)庫非常重要。 除此之外,開發(fā)人員工作站上的數(shù)據(jù)庫工作負(fù)載顯然與生產(chǎn)服務(wù)器不同。 這就是為什么您可能想要實(shí)施以下描述的一些做法。
生成特定于環(huán)境的腳本
在CUBA中,我們生成用于創(chuàng)建和更新應(yīng)用程序數(shù)據(jù)庫的SQL腳本。 在生產(chǎn)服務(wù)器上首次創(chuàng)建數(shù)據(jù)庫之后,一旦模型更改,CUBA框架就會生成更新腳本。
關(guān)于生產(chǎn)中的數(shù)據(jù)庫更新,有一個(gè)特殊的部分 ,請?jiān)谑状紊a(chǎn)之前閱讀它。
最終建議:始終在更新之前執(zhí)行數(shù)據(jù)庫備份。 如果出現(xiàn)任何問題,這將節(jié)省大量時(shí)間和精力。
考慮多租戶
如果您的項(xiàng)目將成為多租戶應(yīng)用程序 ,請?jiān)陧?xiàng)目開始時(shí)將其考慮在內(nèi)。
CUBA通過該插件支持多租戶,它對應(yīng)用程序的數(shù)據(jù)模型和數(shù)據(jù)庫的查詢邏輯進(jìn)行了一些更改。 例如,一個(gè)單獨(dú)的列tenantId被添加到所有特定于Tenant的實(shí)體。 因此,所有查詢都隱式修改為使用此列。 這意味著在編寫本機(jī)SQL查詢時(shí)應(yīng)考慮此列。
請注意,由于上面提到的特定功能,向在生產(chǎn)環(huán)境中運(yùn)行的應(yīng)用程序添加多租戶功能可能很棘手。 為了簡化遷移,請將所有自定義查詢保留在同一應(yīng)用程序?qū)又?#xff0c;最好在服務(wù)中或在單獨(dú)的數(shù)據(jù)訪問層中。
安全注意事項(xiàng)
對于可以被多個(gè)用戶訪問的應(yīng)用程序,安全性起著重要的作用。 為了避免數(shù)據(jù)泄漏,未經(jīng)授權(quán)的訪問等,您需要認(rèn)真考慮安全性。 您可以在下面找到一些原則,這些原則將幫助您改善安全性。
安全編碼
安全性始于防止問題的代碼。 您可以在此處找到有關(guān)Oracle提供的安全編碼的很好的參考。 在下面,您可以從本指南中找到一些(也許很明顯)建議。
準(zhǔn)則3-2 / INJECT-2:避免使用動態(tài)SQL
眾所周知,動態(tài)創(chuàng)建的SQL語句(包括不受信任的輸入)會受到命令注入的影響。 在CUBA中,您可能需要執(zhí)行JPQL語句,因此,也請避免使用動態(tài)JPQL。 如果需要添加參數(shù),請使用適當(dāng)?shù)念惡驼Z句語法:
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();}準(zhǔn)則5-1 / INPUT-1:驗(yàn)證輸入
在使用之前,必須驗(yàn)證來自不受信任來源的輸入。 精心設(shè)計(jì)的輸入可能會導(dǎo)致問題,無論是通過方法參數(shù)還是外部流。 其中一些示例是整數(shù)值溢出和通過在文件名中包含“ ../”序列的目錄遍歷攻擊。 在CUBA中,除了簽入代碼外,您還可以在GUI中使用驗(yàn)證器 。
以上只是安全編碼原理的幾個(gè)示例。 請仔細(xì)閱讀該指南,它將以多種方式幫助您改進(jìn)代碼。
保護(hù)個(gè)人資料的安全
由于法律要求,某些個(gè)人信息應(yīng)受到保護(hù)。 在歐洲,我們有GDPR ,在美國的醫(yī)療應(yīng)用中,有HIPAA要求等。因此,在實(shí)施您的應(yīng)用時(shí)要考慮到這一點(diǎn)。
CUBA允許您設(shè)置各種權(quán)限,并使用角色和訪問組限制對數(shù)據(jù)的訪問。 在后者中,您可以定義各種約束條件 ,以防止未經(jīng)授權(quán)訪問個(gè)人數(shù)據(jù)。
但是提供訪問權(quán)限只是確保個(gè)人數(shù)據(jù)安全的一部分。 數(shù)據(jù)保護(hù)標(biāo)準(zhǔn)和行業(yè)特定要求中有很多要求。 在規(guī)劃應(yīng)用程序的體系結(jié)構(gòu)和數(shù)據(jù)模型之前,請先查看這些文檔。
更改或禁用默認(rèn)用戶和角色
使用CUBA框架創(chuàng)建應(yīng)用程序時(shí),系統(tǒng)中將創(chuàng)建兩個(gè)用戶: admin和anonymous 。 始終在生產(chǎn)環(huán)境中更改其默認(rèn)密碼,然后用戶才能使用該應(yīng)用程序。 您可以手動執(zhí)行此操作,也可以將SQL語句添加到30-....sql初始化腳本中。
使用CUBA文檔中的建議,這些建議將幫助您正確配置生產(chǎn)中的角色。
如果您具有復(fù)雜的組織結(jié)構(gòu),請考慮為每個(gè)分支機(jī)構(gòu)創(chuàng)建本地管理員 ,而不是在組織級別上創(chuàng)建多個(gè)“超級管理員”用戶。
將角色導(dǎo)出到生產(chǎn)
在第一次部署之前,通常需要將角色和訪問組從開發(fā)(或登臺)服務(wù)器復(fù)制到生產(chǎn)服務(wù)器。 在CUBA中,您可以使用內(nèi)置的管理UI來執(zhí)行此操作,而不必手動執(zhí)行。
要導(dǎo)出角色和特權(quán),您可以使用Administration -> Roles屏幕。 下載文件后,您可以將其上傳到應(yīng)用程序的生產(chǎn)版本。
對于訪問組,有一個(gè)類似的過程,但是您需要使用Administration -> Access Groups屏幕。
配置應(yīng)用
生產(chǎn)環(huán)境通常與開發(fā)環(huán)境以及應(yīng)用程序配置不同。 這意味著您需要執(zhí)行一些其他檢查,以確保您的應(yīng)用程序在生產(chǎn)時(shí)能夠平穩(wěn)運(yùn)行。
配置日志
確保已針對生產(chǎn)環(huán)境正確配置了日志記錄子系統(tǒng):日志級別已設(shè)置為所需級別(通常為INFO),并且在應(yīng)用程序重新啟動時(shí)不會刪除日志。 您可以參考文檔以獲取正確的日志設(shè)置和有用的記錄器參考。
如果使用Docker,請使用Docker卷將日志文件存儲在容器外部。
為了進(jìn)行正確的日志記錄分析,您可以部署特殊的工具來收集,存儲和分析日志。 例如ELK stack和Graylog 。 建議將日志記錄軟件安裝到單獨(dú)的服務(wù)器上,以避免對應(yīng)用程序造成性能影響。
在群集配置中運(yùn)行
可以將CUBA應(yīng)用程序配置為在群集配置中運(yùn)行。 如果決定使用此功能,則需要注意您的應(yīng)用程序體系結(jié)構(gòu),否則,您可能會從應(yīng)用程序中得到意外的行為。 我們希望引起您對專門針對集群環(huán)境進(jìn)行調(diào)整的最常用功能的注意:
任務(wù)調(diào)度
如果要在應(yīng)用程序中執(zhí)行預(yù)定任務(wù)(例如每日報(bào)告生成或每周電子郵件發(fā)送),則可以使用相應(yīng)的框架內(nèi)置功能ъ( https://doc.cuba-platform.com/manual-latest /scheduled_tasks.html )。 但是,請想象自己是一位獲得了三封相同營銷電子郵件的客戶。 你快樂嗎? 如果您的任務(wù)在三個(gè)群集節(jié)點(diǎn)上執(zhí)行,則可能會發(fā)生這種情況。 為避免這種情況,最好使用CUBA任務(wù)計(jì)劃程序 ,該程序使您可以創(chuàng)建單例任務(wù)。
分布式緩存
緩存是可以提高應(yīng)用程序性能的東西。 有時(shí)開發(fā)人員嘗試緩存幾乎所有內(nèi)容,因?yàn)楝F(xiàn)在內(nèi)存非常便宜。 但是,當(dāng)您的應(yīng)用程序部署在多臺服務(wù)器上時(shí),緩存將在服務(wù)器之間分配,并且應(yīng)該同步。 同步過程發(fā)生在相對較慢的網(wǎng)絡(luò)連接上,這可能會增加響應(yīng)時(shí)間。 這里的建議–在決定添加更多緩存之前(尤其是在集群環(huán)境中),執(zhí)行負(fù)載測試并衡量性能。
結(jié)論
CUBA平臺簡化了開發(fā),您可能會完成開發(fā)并開始考慮比預(yù)期更早的投入生產(chǎn)。 但是,無論是否使用CUBA,部署都不是一件容易的事。 而且,如果您開始考慮在開發(fā)的早期階段就進(jìn)行部署并遵循本文所述的簡單規(guī)則,那么您的生產(chǎn)方式很可能會很順利,所需的工作量很小,并且不會遇到嚴(yán)重的問題。
翻譯自: https://www.javacodegeeks.com/2020/03/cuba-getting-ready-for-production.html
總結(jié)
- 上一篇: 怎么清理电脑机箱(怎么清理电脑机箱内的灰
- 下一篇: hibernate脏数据_Hiberna