針對ASP.NET Core Web API的先進架構

weixin_33763244發表於2018-06-19
\

本點要點

\\
  • 與傳統的ASP.NET相比,ASP.NET Core的新架構提供了一些好處\\t
  • ASP.NET Core從一開始就包含對依賴注入的支援\\t
  • 單一職責原則簡化了實施和設計。\\t
  • 埠和介面卡模式將業務邏輯與其他依賴項分離\\t
  • 解耦的架構使測試更容易、更健壯\
\\

.NET Core 最初是在2016年釋出的,隨著.NET Core 2.0的釋出,微軟擁有了下一個通用、模組化、跨平臺和開源的平臺主版本。.NET Core已經建立了許多API,在當前版本的.net框架中均可用。它最初是為下一代ASP.NET解決方案而建立的,但現在成了許多其他場景的驅動和基礎,包括物聯網、雲端計算和下一代移動解決方案。在本系列文章中,我們將探討.NET Core的一些好處,以及它如何不僅能使傳統的.NET開發人員受益,還能使所有需要為市場帶來健壯、高效和經濟的解決方案的技術人員受益。

\\

InfoQ的這篇文章是這個系列文章“.NET Core ”的一部分。您可以通過RSS訂閱接收通知。

\\

如今的網際網路與五年前已經完全不同了,更不用說20年前我剛開始做專業開發人員的時候了。今天,Web api連線了由Web應用和移動應用驅動的現代網際網路。有一種技能非常需要,那就是建立其他開發人員也可以使用的健壯的Web api。驅動大多數現代web和移動應用的API都需要具有穩定性和可靠性,以便在流量達到效能限制時仍能繼續服務。

\\

本文的目的是描述ASP.NET Core 2.0 Web API解決方案的體系結構,它使用了Hexagonal架構和埠和介面卡模式。首先,我們來看看.NET Core和ASP.NET Core的新特性,它們對現代Web API很有幫助。

\\

本文示例中的解決方案和所有程式碼都可以在我的GitHub儲存庫 ChinookASPNETCoreAPIHex中找到。

\\

用於Web API的.NET Core 和 ASP.NET Core

\\

ASP.NET Core是微軟在.NET Core的基礎上構建的一個新的web框架,用來擺脫.NET 1.0以來的遺留技術。相比之下,ASP.NET 4.6仍然使用System.Webassembly(它包含了所有WebForms類庫),也因此引入了最近的ASP.NET MVC 5解決方案。通過擺脫這些遺留依賴和從頭開始開發框架,併為跨平臺執行進行了架構設計,ASP.NET Core 2.0為開發人員提供了更好的效能。使用ASP.NET Core 2.0,你的解決方案將在Linux上和Windows上均可有效運轉。

\\

更多關於.NET Core和ASP.NET Core的好處,你可以閱讀本系列的其他三篇文章。第一篇是Maarten Balliauw的《效能是.NET的核心特性》、Chris Klug的《ASP.NET Core --簡潔的力量》,以及最後Eric Boyd的《Azure和.NET Core的完美契合》。

\\

體系架構

\\

構建一個優秀的API依賴於偉大的架構。我們將從ASP.NET Core 的內建功能來研究API設計和開發的許多方面以形成哲學和最終設計模式的架構。這個架構背後有很多計劃和想法,讓我們開始吧。

\\

依賴注入

\\

在我們深入研究ASP的架構之前.NET Core Web API解決方案,我想討論一下我所認為的使.NET核心開發人員過得更好的單一好處;即依賴注入(DI)。現在,我知道你會說我們在.NET框架和ASP.NET解決方案中有依賴注入。我同意,但是我們過去使用的依賴注入是來自第三方的商業提供商或者開源庫。他們做得很好,但是對於.NET開發人員來說,有一個很陡峭的學習曲線,並且所有的依賴注入庫都有自己獨特的處理方法。今天,有了.NET Core,我們從一開始就將依賴注入整合到框架中了。此外,它的用法非常簡單,它是立即可用的。

\\

我們需要在API中使用依賴注入的原因是,它允許我們有最好的經驗來解耦架構層,並允許我們模擬資料層,或者為API構建多個資料來源。

\\

要使用.NET Core 依賴注入 框架,請確保您的專案引用了Microsoft.AspNetCore.AllNuGet包(它包含對Microsoft.Extnesions.DependencyInjection.Abstractionspackage的依賴關係)。這個包提供了對IServiceCollection介面的訪問,該介面具有一個System.IServiceProvider 介面,您可以呼叫GetService\u0026lt;TService\u0026gt;從IServiceCollection介面獲得所需的服務,需要新增專案所需的服務。

\\

要了解更多關於.NET Core 依賴注入的資訊,我建議您閱讀以下關於MSDN的文件:ASP.NET中依賴注入的介紹

\\

現在我們來看看為什麼要像我一樣做API架構設計的原理。設計任何架構的兩個方面都依賴於這兩個概念:允許深度可維護性,以及在解決方案中使用經過驗證的模式和架構。

\\

API的可維護性

\\

對於任何工程過程來說,可維護性是指一個產品易於被維護:發現缺陷、糾正發現的缺陷、無需替換仍在工作的元件即可修復或更換有缺陷的元件、預防意外故障、最大限度地提高產品的使用壽命、有能力滿足新的需求、使未來的維護更容易,以及能應對環境變化。如果沒有精心規劃和可執行的架構,就很難做到以上種種。

\\

可維護性是一個長期的問題,應該從您的API的遠景來看。考慮到這一點,你需要做出決定,實現未來的願景而不是走那些看上去能過得更輕鬆的捷徑。在一開始就做出艱難的決定將使你的專案有一個很長的生命週期,並提供使用者所需的好處。

\\

什麼使軟體架構具有高可維護性?如何評估API是否可被維護?

\\
  • 我們的體系結構是否允許最小化對系統其他領域的影響甚至為零?\\t
  • 對API的除錯應該是容易的,不需要設定困難。我們應該建立模式並通過常用的方法(例如瀏覽器除錯工具)。\\t
  • 測試應該是自動化的,並且清晰明瞭,不能太複雜。\

介面和實現

\\

我的API體系結構的關鍵是使用C#介面來支援其他實現。如果您已經用C#編寫了.NET程式碼,那麼您可能已經使用了介面。我在解決方案中使用介面在領域層中構建一個契約,該契約保證我為API開發的任何資料層都遵循資料儲存庫的契約。它還允許我的API專案中的控制器遵守另一個已設立的契約,以獲得正確的方法來處理領域專案的Supervisor中的API方法。介面對於.NET Core 是非常重要的,如果您需要了解更多的資訊,請點選此處

\\

埠和介面卡模式

\\

我們希望整個API解決方案中的物件具有單一職責。這將使我們在需要修復缺陷或增強程式碼時讓物件保持簡單和易於修改。如果您的程式碼中有一些“程式碼異味”,那麼您可能違反了單一責任原則。一般情況下,我關注介面契約的實現的長度和複雜性。我的方法中沒有程式碼行限制,但是如果它已經超過了您IDE中的一個檢視,那麼它可能就太長了。此外,我還檢查方法的圈複雜度,以確定專案方法和函式的複雜性。

\\

埠和介面卡模式(又稱六角形架構)可以解決業務邏輯與其他依賴項(如資料訪問或API框架)耦合過於緊密的問題。使用此模式將允許您的API解決方案具有清晰的邊界、具有單一職責的良好命名的物件,最終使其更容易開發和維護。

\\

我們可以很直觀地把這個模式看作一個洋蔥,埠位於六邊形的外部,而介面卡和業務邏輯的位置更靠近核心。我將架構的外部連線視為埠。被消費的API端點或Entity Framework Core 2.0所使用的資料庫連線將成為典型的埠範例,而內部資料儲存庫則是介面卡。

\\

\\

接下來,讓我們看看架構的邏輯部分和一些演示程式碼示例。

\\

21d60d0cdee996bf3a002594c9ec6111.png

\\

領域(Domain)層

\\

在檢視API和領域層之前,我們需要解釋如何通過介面和API業務邏輯的實現構建契約。我們來看看領域層。領域層具有以下功能:

\\
  • 定義將在整個解決方案中使用的實體物件。這些模型將表示資料層的資料模型(DataModel)。\\t
  • 定義檢視模型(ViewModel),將由API層針對HTTP請求和響應作為單個物件或物件集來使用。\\t
  • 定義介面,我們的資料層可以通過這些介面實現資料訪問邏輯。\\t
  • 實現將包含從API層呼叫的方法的Supervisor。每個方法都代表一個API呼叫,並將資料從注入的資料層轉換為檢視模型以返回。\

我們的域實體物件代表我們用來儲存和檢索用於API業務邏輯的資料的資料庫。每個實體物件都將包含SQL表中的屬性。如下即為照片實體Album。

\\
\public sealed class Album\{\    public int AlbumId { get; set; }\    public string Title { get; set; }\    public int ArtistId { get; set; }\        \    public ICollection\u0026lt;Track\u0026gt; Tracks { get; set; } = new HashSet\u0026lt;Track\u0026gt;();\    public Artist Artist { get; set; }\}
\\

SQL資料庫中的Album表有三表:AlbumId、Title和ArtistId。這三個屬性是專輯實體的一部分,另外還有藝術家的名字以及相關藝術家和一組相關歌曲。正如我們將在API體系結構的其他層中看到的,我們將針對該專案中的檢視模型構建此實體物件的定義。

\\

檢視模型是實體的擴充套件,並幫助為api的使用者提供更多的資訊。讓我們看看檢視模型。它與相簿實體非常相似,但具有額外的屬性。在API的設計中,我確定每個相簿應該在從API返回的有效負載中包含藝術家的名字。這能讓API使用者擁有關於相簿的關鍵資訊,而不必在資料載荷中再傳遞Artist檢視模型(特別是當我們返回大量Album時)。下面是我們的Album檢視模型的一個示例。

\\
\public class AlbumViewModel\{\    public int AlbumId { get; set; }\    public string Title { get; set; }\    public int ArtistId { get; set; }\    public string ArtistName { get; set; }\\    public ArtistViewModel Artist { get; set; }\    public IList\u0026lt;TrackViewModel\u0026gt; Tracks { get; set; }\}
\\

在領域層中另一部分需要開發的是契約,它們會經過該層中為每個實體定義的介面。同樣,我們將使用Album實體來展示所定義的介面。

\\
\public interface IAlbumRepository : IDisposable\{\\tTask\u0026lt;List\u0026lt;Album\u0026gt;\u0026gt; GetAllAsync(CancellationToken ct = default(CancellationToken));\Task\u0026lt;Album\u0026gt; GetByIdAsync(int id, CancellationToken ct = default(CancellationToken));\       Task\u0026lt;List\u0026lt;Album\u0026gt;\u0026gt; GetByArtistIdAsync(int id, CancellationToken ct = default(CancellationToken));\       Task\u0026lt;Album\u0026gt; AddAsync(Album newAlbum, CancellationToken ct = default(CancellationToken));\       Task\u0026lt;bool\u0026gt; UpdateAsync(Album album, CancellationToken ct = default(CancellationToken));\       Task\u0026lt;bool\u0026gt; DeleteAsync(int id, CancellationToken ct = default(CancellationToken));\}
\\

如上例所示,介面定義了為Album實體實現資料訪問方法所需的方法。每個實體物件和介面都有良好的定義和簡單化,使下一層可以得到良好的定義。

\\

最後,領域專案的核心是Supervisor類。它的用途是在實體和檢視模型之間進行轉換,並在API端點和資料訪問邏輯之外執行業務邏輯。讓Supervisor來處理這些還將隔離邏輯,使轉換和業務邏輯能夠進行單元測試。

\\

檢視獲取和傳遞單個Album到API端點的Supervisor方法,我們可以看到將API前端連線到資料訪問的邏輯注入到了Supervisor中,而仍然保持每個Album是獨立的。

\\
\public async Task\u0026lt;AlbumViewModel\u0026gt; GetAlbumByIdAsync(int id, CancellationToken ct = default(CancellationToken))\{\    var albumViewModel = AlbumCoverter.Convert(await _albumRepository.GetByIdAsync(id, ct));\    albumViewModel.Artist = await GetArtistByIdAsync(albumViewModel.ArtistId, ct);\    albumViewModel.Tracks = await GetTrackByAlbumIdAsync(albumViewModel.AlbumId, ct);\    albumViewModel.ArtistName = albumViewModel.Artist.Name;\    return albumViewModel;\}
\\

在領域專案中維護大部分程式碼和邏輯會使每個專案保持並遵守單一職責原則。

\\

資料層

\\

我們將看到的API體系結構的下一層是資料層。在我們所示例的解決方案中,使用的是Entity Framework Core 2.0。這意味著我們不僅擁有已定義的Entity Framework Core的DBContext,還有為SQL資料庫中的每個實體生成的資料模型。如果我們以專輯實體的資料模型為例來看,會發現在資料庫中存有三個屬性,還有包含一組與專輯相關的歌曲,以及藝術家物件的相關屬性。

\\

雖然您可以擁有大量的資料層實現,但請記住,它必須遵守在領域層上記錄的要求;每個資料層實現必須與領域層中詳細的檢視模型和儲存庫介面一起工作。我們為API開發的體系結構使用倉儲模式將API層連線到資料層。使用依賴注入(正如我們前面討論過的)對我們實現的每個儲存庫物件進行了處理。我們將討論在著眼於API層時如何使用依賴項注入和程式碼。資料層的關鍵是使用領域層中開發的介面實現每個實體儲存庫。以領域層的專輯儲存庫為例,它就是實現了IAlbumRepository介面。每個儲存庫都將注入DBContext,允許使用實體框架核心訪問SQL資料庫。

\\
\public class AlbumRepository : IAlbumRepository\{\    private readonly ChinookContext _context;\\    public AlbumRepository(ChinookContext context)\    {\        _context = context;\    }\\    private async Task\u0026lt;bool\u0026gt; AlbumExists(int id, CancellationToken ct = default(CancellationToken))\    {\return await GetByIdAsync(id, ct) != null;\    }\\    public void Dispose()\    {\        _context.Dispose();\    }\\    public async Task\u0026lt;List\u0026lt;Album\u0026gt;\u0026gt; GetAllAsync(CancellationToken ct = default(CancellationToken))\    {\        return await _context.Album.ToListAsync(ct);\    }\\    public async Task\u0026lt;Album\u0026gt; GetByIdAsync(int id, CancellationToken ct = default(CancellationToken))\    {\        return await _context.Album.FindAsync(id);\    }\\    public async Task\u0026lt;Album\u0026gt; AddAsync(Album newAlbum, CancellationToken ct = default(CancellationToken))\    {\        _context.Album.Add(newAlbum);\        await _context.SaveChangesAsync(ct);\        return newAlbum;\    }\\    public async Task\u0026lt;bool\u0026gt; UpdateAsync(Album album, CancellationToken ct = default(CancellationToken))\    {\        if (!await AlbumExists(album.AlbumId, ct))\            return false;\        _context.Album.Update(album);\\        _context.Update(album);\        await _context.SaveChangesAsync(ct);\        return true;\    }\\    public async Task\u0026lt;bool\u0026gt; DeleteAsync(int id, CancellationToken ct = default(CancellationToken))\    {\        if (!await AlbumExists(id, ct))\ \t     return false;\        var toRemove = _context.Album.Find(id);\        _context.Album.Remove(toRemove);\        await _context.SaveChangesAsync(ct);\        return true;\    }\\    public async Task\u0026lt;List\u0026lt;Album\u0026gt;\u0026gt; GetByArtistIdAsync(int id, CancellationToken ct = default(CancellationToken))\    {\        return await _context.Album.Where(a =\u0026gt; a.ArtistId == id).ToListAsync(ct);\    }\}
\\

擁有封裝所有資料訪問的資料層將有助於更好地測試API。我們可以構建多個資料訪問實現:一個用於SQL資料庫儲存,另一個可以用於雲NoSQL 儲存模式,最後一個用於解決方案中的單元測試的模擬儲存實現。

\\

API層

\\

我們將看到的最後一層是您的API使用者將發生互動的區域。這一層包含Web API端點邏輯的程式碼,包括控制器。這個解決方案的API專案將有一個單獨的職責,那就是處理web伺服器接收到的HTTP請求並返回HTTP響應,無論成功還是失敗。在這個專案中,將會有非常少的業務邏輯。我們將處理在領域或資料專案中發生的異常和錯誤,以有效地與API的使用者進行通訊。此通訊將使用HTTP響應程式碼和在HTTP響應報文中返回的任何資料。

\\

在ASP.NET Core 2.0 Web API,路由是使用Routing屬性來處理的。如果您需要了解更多關於ASP.NET Core中Routing屬性的內容,請移步此處。我們還使用依賴項注入將Supervisor分配給每個控制器。每個控制器的操作方法都有一個相應的Supervisor方法,用於處理API呼叫的邏輯。下面我有一個Album控制器的片段來展示這些概念。

\\
\[Route(\"api/[controller]\")]\public class AlbumController : Controller\{\    private readonly IChinookSupervisor _chinookSupervisor;\\    public AlbumController(IChinookSupervisor chinookSupervisor)\    {\        _chinookSupervisor = chinookSupervisor;\    }\\    [HttpGet]\    [Produces(typeof(List\u0026lt;AlbumViewModel\u0026gt;))]\    public async Task\u0026lt;IActionResult\u0026gt; Get(CancellationToken ct = default(CancellationToken))\    {\        try\        {\            return new ObjectResult(await _chinookSupervisor.GetAllAlbumAsync(ct));\        }\        catch (Exception ex)\        {\            return StatusCode(500, ex);\        }\    } \\    ...\}
\\

這個解決方案的Web API專案非常簡略。我努力讓這個解決方案中的程式碼儘可能的少,因為將來它可以被另一種互動形式所替代。

\\

結論

\\

正如我所展示的,設計和開發一個偉大的ASP.NET Core 2.0 Web API解決方案具有洞察力,以便擁有一個解耦的體系結構,該體系結構將允許每個層都是可測試的,並遵循單一的職責原則。我希望我的資訊將允許您建立和維護您的產品Web api,以滿足您的組織的需要。

\\

關於作者

\\

05b2b1a72229589f5dc2b47705b17235.jpgChris Woodruff (Woody) 擁有密歇根州立大學工程學院的電腦科學學位。Woody已經開發和架構軟體解決方案超過20年,並且曾經致力於許多不同的平臺和工具。他是一個社群領袖,為GRDevNight、GRDevDay、West Michigan Day of .NET和CodeMash之類的活動貢獻過力量。他還幫助把廣受歡迎的Give Camp活動帶到西密歇根,那裡的技術專業人士提供他們的時間和發展專業知識,以幫助當地的非營利組織。作為一個演講者和播客作者,Woody已經講過和討論了很多話題,包括資料庫設計和開源。他在Visual C#、資料平臺和SQL方面一直是微軟的MVP,並在2010年被公認為全球最優秀的20個MVPs之一。Woody是JetBrains的開發者,並且在北美推廣.NET,.NET Core和JetBrains的產品。

\\

.NET Core 最初是在2016年釋出的,隨著.NET Core 2.0的釋出,微軟擁有了下一個通用、模組化、跨平臺和開源的平臺主版本。.NETCore已經建立了許多API,在當前版本的.net框架中均可用。它最初是為下一代ASP.NET解決方案而建立的,但現在成了許多其他場景的驅動和基礎,包括物聯網、雲端計算和下一代移動解決方案。在本系列文章中,我們將探討.NET Core的一些好處,以及它如何不僅能使傳統的.NET開發人員受益,還能使所有需要為市場帶來健壯、高效和經濟的解決方案的技術人員受益。

\\

InfoQ的這篇文章是“.NET Core”這個系列的一部分。您可以通過RSS訂閱接收通知。

\\

檢視英文原文:Advanced Architecture for ASP.NET Core Web API

相關文章