Kiss架構:Springboot + Angular - Pasquale Paola
就像維基百科建議的那樣,KISS是一個縮寫
保持簡單,愚蠢
作為美國海軍在1960年提出的設計原則.KISS原則指出,如果保持簡單而不是複雜化,大多數系統都能發揮最佳作用; 因此,簡單性應該是設計中的關鍵目標,並且應該避免不必要的複雜性。
根據我的經驗,我研究了許多型別的技術,有機會檢查原始碼,一般來說,從客戶端(可以是SPA或本機應用程式)和伺服器端開發應用程式。 。依靠這樣的經歷,我想開發一個簡單的架構,包括尊重基本的格局在CRUD上下文。我將說明這個架構的基礎,作為我所有專案的起點。
在設計中我記住了KISS概念,從這個角度來看,我將提供5層,稱為層,具有特定的職責任務:
- 前端
- API
- 商業邏輯
- 整合層
- DAO
我使用多層架構模型。列表中的層順序與資訊流嚴格關聯,從前端一直到達DAO!
本文引用的專案可通過以下連結下載:https://github.com/paspao/springboot-kiss-architecture
git clonehttps://github.com/paspao/springboot-kiss-architecture
該專案使用maven(即父類和子類)進行管理; 即使在Angular中開發的前端被包含在maven構建階段中以建立單個工件(下面的FRONTEND段中提供了更多詳細資訊)。
為了深入瞭解每一層,我更喜歡使用BottomUp方法,所以讓我們從資料開始。
DAO
資料訪問物件。我在談論CRUD應用程式,即要收集和處理的資料。這個模組代表了最深刻的一點。該層執行資料,它描述實體和訪問邏輯。注意:插入,修改,刪除和顯示資料的簡單資料訪問邏輯,不與任何其他層繫結; 它是最深層的,並且它與它的任何兄弟都沒有依賴關係,它不處理應用程式的特定方面,例如授權,事務或其他:也只是訪問資料。在Springboot上下文中,我正在執行實體和儲存庫範例。
@Configuration @ComponentScan(<font>"org.ska.dao"</font><font>) @EntityScan(basePackages = {</font><font>"org.ska.dao.entity"</font><font>}) @EnableJpaRepositories(basePackages = {</font><font>"org.ska.dao.repository"</font><font>}) @EnableAutoConfiguration @EnableTransactionManagement <b>public</b> <b>class</b> KissDaoConfiguration { } </font>
所以我定義了元件,實體和儲存庫的位置。此外,我啟用了事務,因此使用DAO模組的任何人都不必擔心配置DAO模組的方面。
整合
簡單的CRUD資料管理是不夠的:我們可能需要與不依賴於我們資料的其他系統(例如JAX-WS或JAX-RS服務)或具有不同協議的特定列印系統等進行互操作。此元件包括所有這些互動/整合都沒有對應用程式上下文的特定繫結,以保證您具有非常高的可重用性(如DAO模組,此層也是葉型別)。
@Configuration @ComponentScan(basePackages = {<font>"org.ska.integration.beans"</font><font>}) <b>public</b> <b>class</b> KissIntegrationConfiguration { @Bean <b>public</b> GeoApiContext geocoder(){ GeoApiContext context = <b>new</b> GeoApiContext.Builder() .apiKey(</font><font>"Your apikey"</font><font>) .build(); <b>return</b> context; } } </font>
這裡我展示了模組配置的中心點:只有一個對服務定義的引用和第三方服務的例項(GeoApiContext)。
業務邏輯
稍後識別要處理的資料時,每個應用程式必須處理定義到DAO層中的實體之間的互動邏輯。您必須將使用者需求與應用程式邏輯相結合,將它們分解,然後向上層公開簡單且可讀的簽名。因此,這一層允許開發人員進行一些處理,而無需詳細說明資料庫的結構或下面的整合。
我們在這裡發現DTO的定義和用法有助於掩蓋儲存在資料庫系統或各種整合bean中的資料:為什麼?
有一些原因:首先,它是一種隱藏敏感資料的資訊形式(例如密碼,時間戳或資料一致性所需的其他資訊,但對終端使用者沒有)。如果需要詳細說明返回的資料(如從多個源組合的資料),DTO有助於以合適的方式構建這些資料。
另一個方面是資料的序列化:在某些上下文中,您必須轉換資料庫中存在的資訊,以使其可用於人類。因此,開發人員必須“汙染” 實體使用序列化邏輯,其目的應該只是表示資料,例如:資料庫系統上的DATE欄位可能是一個數字,但我們以可列印的方式表示它,所以我們可能會使用格式化註釋; 這是一個解決方案!但在這種情況下,我們將序列化的各個方面連結到一個實體,從長遠來看,這個解決方案將導致無法使用的程式碼。哈羅德·阿貝爾森說:
必須編寫程式供人們閱讀,並且只有程式才能執行。
DTO允許面對列出的問題,建立類似“緩衝區”的東西,因此更鬆散耦合和更多可重用性。
總而言之,BUSINESS LOGIC與DAO層和INTEGRATION層進行通訊,建立協同作用並在它們之間進行互動。此外,它將邏輯和資料轉換帶入DTO,可供其他層使用。警告:業務邏輯層使用定義的DTO,同樣適用於返回的資料。它們絕不是其他層中定義的物件,因此要確保我們上面所說的內容並且能夠處理返回的資料。
該層的另一個特徵是事務管理:由於它實現了業務邏輯,因此能夠確定對資料的操作是否成功,因此定義了操作的“ 事務性 ”。
以下是業務邏輯層的@Configuration:
@Configuration @ComponentScan(basePackages = {<font>"org.ska.business"</font><font>}) @Import({KissDaoConfiguration.<b>class</b>, KissIntegrationConfiguration.<b>class</b>}) <b>public</b> <b>class</b> KissBusinessConfiguration { @Bean <b>public</b> Mapper dozerMapper(){ Mapper dozerBeanMapper =DozerBeanMapperBuilder.buildDefault(); <b>return</b> dozerBeanMapper; } } </font>
它是唯一一個直接連結到DAO和INTEGRATION層的模組,因此必須匯入配置才能使用它們。此外,為了加快實體和DTO之間的對映,使用為此而生成的框架是一種很好的做法,避免了長期和不可讀的setter和getter程式碼塊; 在我的例子中,我使用了一個名為Dozer的對映框架。
API
在這層中,呈現邏輯,它代表了我們應用程式的入口點,至少從伺服器的角度來看。JAX-RS或JAX-WS等服務的定義主要是以XML,JSON或其他方式呈現資料。它僅與BUSINESS LOGIC層進行對話:服務只調用Business層提供的一個或多個服務,它永遠不會直接使用INTEGRATION或DAO層,也不會使用其中定義的物件,以避免緊密耦合和義大利麵條程式碼。
它涉及身份驗證和授權的管理:這裡可以確定誰可以執行操作:在下面的層中要複雜得多,或者確定哪些角色尚不可知的資訊。
API總是需要一些文件:缺乏REST世界之一是缺少這些服務的通用描述符。保證這方面的技術是Swagger(現在是OpenAPI):它允許記錄API,但是生成的文件也可以重用來生成客戶端部分,因此不僅僅是描述性的部分。例如,在我的情況下,帶有REST服務的通訊層完全由服務的Swagger描述生成:在FRONTEND模組中,有一個remote-services資料夾,其中包含工具:https://editor.swagger.io .
在api層的配置類中,我匯入業務層並設定Swagger文件的生成。
@Configuration @Import(KissBusinessConfiguration.<b>class</b>) @EnableSwagger2 <b>public</b> <b>class</b> KissApiConfiguration { @Autowired <b>private</b> Environment env; @Bean <b>public</b> Docket api() { <b>return</b> <b>new</b> Docket(DocumentationType.SWAGGER_2) .select() <font><i>//.apis(RequestHandlerSelectors.any())</i></font><font> .apis(RequestHandlerSelectors.basePackage(</font><font>"org.ska.api.web"</font><font>)) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } <b>private</b> ApiInfo apiInfo() { <b>return</b> <b>new</b> ApiInfo( </font><font>"Contact management REST API"</font><font>, </font><font>"API"</font><font>, env.getProperty(</font><font>"info.version"</font><font>), <b>null</b>, <b>new</b> Contact(</font><font>"Pasquale Paola"</font><font>, </font><font>"https://www.linkedin.com/in/pasqualepaola/"</font><font>, </font><font>"[email protected]"</font><font>), <b>null</b>, <b>null</b>, Collections.emptyList()); } } </font>
前端
它代表單頁應用程式:這種型別的應用程式必須與應用程式完全分離,並且Rest技術的使用已經保證了這一方面,但有必要注意與遠端服務的通訊是如何實現的。我經常陷入非常糟糕的組織和管理用於呼叫遠端服務的各種HTTP客戶端,我的意思是在整個應用程式中找到的引用。
為了解決這個問題並使SPA與代表與遠端服務通訊的所有內容嚴格分開,如前所述,我使用Swagger技術生成一個允許與Rest API通訊的存根。所以開發人員將使用Swagger生成的東西,主要是因為它提供了大量現成的程式碼,具有不同的使用選項,而你不再需要重寫它。此外,邏輯將在其他地方實現,因為遠端通訊部分(Stub)將不斷重新生成,並且開發人員不會夢想在可能被覆蓋的源中實現自己的邏輯(我希望)。
為了確保用Angular編寫的應用程式可以包含在Maven專案的構建週期中,我確保通過新增pom.xml檔案即使FRONTEND也成為Maven模組。這個模組不會產生任何工件,所以包裝將是pom型別,但是這樣我就可以將它插入到maven構建中並與其兄弟姐妹建立依賴關係。為了能夠在Maven上下文中整合Angular構建,我使用了一個名為frontend-maven-plugin的外掛:它允許安裝Node和Npm例項
<execution> <id>install node and npm</id> <goals> <goal>install-node-and-npm</goal> </goals> <phase>generate-resources</phase> </execution> <execution> <id>npm install</id> <goals> <goal>npm</goal> </goals> <configuration> <arguments>install</arguments> </configuration> </execution>
以及隨後呼叫Angular CLI來管理依賴關係並構建。
<execution> <id>npm build</id> <goals> <goal>npm</goal> </goals> <phase>generate-resources</phase> <configuration> <arguments>run build</arguments> </configuration> </execution>
當呼叫Npm構建任務時,控制元件將從Angular CLI中獲取,如package.json中所述:
... <font>"build"</font><font>: </font><font>"ng build --prod --progress --build-optimizer --delete-output-path --base-href /kiss/ui/ --output-path dist/resources/static/ui"</font><font> ... </font>
輸出路徑設定為dist / resources / static / ui,路徑dist / resources也配置為前端模組的資源。結合下面的層API配置,它允許在Springboot應用程式中注入Angular構建的結果。在輸出路徑(package.json 中的build命令)中有一個特殊目錄 ... / static / ...,其中一個是Springboot允許定義靜態內容的目錄。
<resources> <resource> <directory>../frontend/dist/resources</directory> </resource> <resource> <directory>src/main/resources</directory> <filtering><b>true</b></filtering> </resource> </resources> ...
Build
mvn clean install
Run
java -jar api/target/api-0.0.1-SNAPSHOT.jar
http://localhost:8080/kiss/
點選標題見原文!