[翻譯] 基於.NET Core構建微服務 第五部分:Marten域聚合的理想倉庫
原文:Building Microservices On .NET Core – Part 5 Marten An Ideal Repository For Your Domain Aggregates
作者:Wojciech Suwała, Head Architect, ASC LAB
時間:2019年4月11日
這是我們系列中有關在.NET Core上構建微服務的第五篇文章。 在第一篇文章中,我們介紹了該系列並準備了計劃:業務案例和解決方案體系結構。 在第二篇文章中,我們描述瞭如何使用CQRS模式和MediatR庫來構建一個微服務的內部架構。 在第三篇文章中,我們描述了服務發現在基於微服務的體系結構中的重要性,並介紹了Eureka的實際實現。 在
在本文中,我們將退一步,討論資料訪問以及如何有效地持久儲存資料。
完整解決方案的原始碼可以在我們的GitHub上找到。
永續性是一個已解決的問題,不是嗎?
當.NET Framework的第一個版本於2002年左右釋出時,我們有兩個用於資料訪問的主要API:資料集和資料讀取器。資料集,在資料庫中表的記憶體表示形式中,另一方面,資料讀取器答應讓您快速讀取資料,但必須手動將其推入物件中。許多開發人員發現了反射的力量,幾乎每個人都開發了自己的ORM。過去,我的團隊很少評估這樣的框架,但是對於我們來說,這些框架似乎都不是合適的解決方案,因為我們正在為保險業開發複雜的應用程式。因此,我們決定在每次插入,更新和搜尋時都使用DataReaders和手工編碼的SQL。幾年後,我們建立了今天稱為微型ORM的產品。即使使用我們自己開發的簡單工具,我們也可以消除大約70%的資料訪問程式碼。然後是NHibernate時代。作為具有Java經驗的開發人員,我很嫉妒Java同事擁有如此強大的庫,當NHibernate的早期版本可用時,我很想嘗試一下。當我們開始在生產中使用NHibernate時,我認為它是2.0版。多年來,NHibernate一直是我們的首選,並在許多專案中為我們提供了幫助。這是令人難以置信的靈活且功能豐富的庫。但是微軟決定實施自己的專有解決方案–實體框架。隨著它的大力推廣,許多.NET商店決定改用EF和NHibernate,因此社群開始萎縮。
然後,Microsoft引入了.NET Core,一切都變了。他們沒有移植現有的實體框架,而是決定從頭開始開發它的新版本。結果,.NET Core的第一個版本實際上沒有用於資料訪問的企業級解決方案。當前,我們幾乎已經擁有.NET Core 3,而EF Core仍然缺少您希望成熟的ORM提供的許多功能。 NHibernate最終登陸.NET Core,但我認為它不會因為周圍的社群小得多而重新流行起來。與今天的ORM相比,NHibernate也具有很大的侵入性,例如,它迫使您將所有屬性虛擬化,因此可以由ORM代理。
.NET Core的到來和微服務的日益普及完全改變了.NET體系結構的格局。您現在可以在Linux上進行開發和部署。在.NET開發人員中,MS SQL以外的資料庫的使用正變得越來越流行。
微服務還增加了多語言永續性流行度。開發人員意識到他們可以將不同的資料儲存用於不同種類的服務。有文件資料庫,圖形資料庫,事件儲存和其他型別的資料庫相關解決方案。
如您所見,有很多選項可供選擇,在本文中,我想談一談使用關係資料庫作為文件資料庫,以充分利用兩者的優勢。在Marten的幫助下,您可以實現這一目標。
什麼是Marten?
Marten是一個客戶端庫,允許.NET開發人員將Postgresql用作文件資料庫和事件儲存。它由傑里米·米勒(Jeremy Miller)於2015年10月左右某個時候啟動,以替代RavenDB資料庫,但不僅限於此。
如果您曾經使用過像MongoDB或RavenDB這樣的文件資料庫,您就會知道它為您帶來了出色的開發人員體驗,尤其是易用性和開發速度,但是還存在一些與效能和資料一致性相關的問題。
使用Marten,您可以輕鬆地將關係資料庫用作文件之一,並具有完全的ACID合規性和廣泛的LINQ支援。
從我的角度來看,對於這種方法,有一個特定的用例似乎是理想的。如果您正在練習域驅動的設計並將域模型劃分為小的聚合,則可以將聚合視為文件。如果您採用這種方法,並與Marten之類的庫結合使用,則持久化,載入和查詢聚合幾乎不需要任何程式碼。由於符合ACID,因此您可以在同一事務中修改和儲存許多聚合,而這在許多文件資料庫中是不可能的。使用關係資料庫還可以簡化基礎架構管理,因為您仍然可以依靠熟悉的工具進行備份和監視。
更重要的是,您的域模型不受ORM功能的限制。
Vaughn Vernon的文章“理想的域驅動設計聚合儲存”中描述了將聚合作為JSON儲存在關係資料庫中的想法,從中我從中獲得了這篇文章標題的靈感。
使用Marten
將Marten新增到專案
與往常一樣,我們首先使用NuGet將Marten依賴項新增到我們的專案中。
Install-Package Marten
接下來需要做的是將連線字串新增到PostgreSQL資料庫中的appsettings.json
。
{
"ConnectionStrings": {
"PgConnection": "User ID=lab_user;Password=*****;Database=lab_netmicro_payments;Host=localhost;Port=5432"
}}
我們還需要安裝PostgreSQL 9.5+資料庫伺服器。
設定Marten
現在我們可以建立Marten。 我們將看一下取自PaymentService的示例程式碼。
在我們的解決方案中,我們決定將域邏輯與永續性細節分開,為此我們引入了兩個介面。
public interface IPolicyAccountRepository
{
void Add(PolicyAccount policyAccount);
Task FindByNumber(string accountNumber);
}
第一個介面代表PolicyAccount聚合的儲存庫。 在這裡,我們使用倉庫模式,如Eric Evans的DDD Blue Book中所述。 我們的儲存庫提供了用於儲存資料的介面,因此我們可以將其用作簡單的收集類。 請注意,我們不建立通用儲存庫。 如果我們正在進行域驅動的設計,則儲存庫應該是域語言的一部分,並且應該僅公開域程式碼所需的操作。
第二個介面表示工作單元模式(Unit Of Work Pattern)–一種服務,該服務跟蹤載入的物件並讓我們保留更改。
public interface IDataStore : IDisposable
{
IPolicyAccountRepository PolicyAccounts { get; }
Task CommitChanges();
}
資料儲存介面使我們能夠訪問儲存庫,因此我們可以從儲存庫中新增和檢索策略帳戶物件,並允許我們將更改提交到永續性儲存。
讓我們看看如何使用Marten來實現這些介面。但是在開始之前,我們必須瞭解Marten中的兩個基本概念:DocumentStore
和DocumentSession
。
第一個代表我們文件儲存的配置。它保留配置資料,例如連線字串,序列化設定,模式定製,對映資訊。
DocumentSession
代表我們的工作單元。它負責開啟和管理資料庫連線,針對資料庫執行SQL語句,載入文件,跟蹤載入的文件以及最後將更改儲存回資料庫。首先,建立一個DocumentStore
例項,然後可以要求它建立一個DocumentSession
例項,最後可以使用DocumentSession
在資料庫中建立,載入,修改和儲存文件。
DocumentSession
有三種實現:
- 輕量級會話 – 不跟蹤更改的會話,
- 標準會話 – 具有身份對映跟蹤的會話,但沒有更改跟蹤
- 髒跟蹤會話 – 一種帶有身份對映和髒跟蹤的會話。
髒跟蹤是通過比較最初從資料庫載入的JSON和從聚合生成的JSON來實現的,因此您必須瞭解效能和記憶體成本。在我們的程式碼中,我們將使用輕量級文件會話。
您可以從Marten的官方文件中瞭解更多資訊。
現在我們知道了基礎知識。我們可以建立MartenInstaller
類,將在Startup類中使用該類來初始化和連線所有必需的片段。
public static class MartenInstaller
{
public static void AddMarten(this IServiceCollection services, string cnnString)
{
services.AddSingleton(CreateDocumentStore(cnnString));
services.AddScoped<Domain.IDataStore, MartenDataStore>();
}
private static IDocumentStore CreateDocumentStore(string cn)
{
return DocumentStore.For(_ =>
{
_.Connection(cn);
_.DatabaseSchemaName = "payment_service";
_.Serializer(CustomizeJsonSerializer());
_.Schema.For().Duplicate(t => t.PolicyNumber,pgType: "varchar(50)", configure: idx => idx.IsUnique = true);
});
}
private static JsonNetSerializer CustomizeJsonSerializer()
{
var serializer = new JsonNetSerializer();
serializer.Customize(_ =>
{
_.ContractResolver = new ProtectedSettersContractResolver();
_.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
});
return serializer;
}
}
此處的關鍵方法是CreateDocumentStore
。 它建立一個文件儲存例項並對其進行配置。
DocumentStore.For(_ =>
{
_.Connection(cn); (1)
_.DatabaseSchemaName = "payment_service"; (2)
_.Serializer(CustomizeJsonSerializer()); (3)
_.Schema.For().Duplicate(t => t.PolicyNumber,pgType: "varchar(50)", configure: idx => idx.IsUnique = true); (4)
});
在這裡,我們:
- 提供到Postgresql資料庫的連線字串。
- 自定義架構名稱(如果不這樣做,則將在公共架構中建立用於儲存文件的表)。
- 自定義
JsonSerializer
,以便它可以序列化受保護的屬性(這很重要,我們正在嘗試根據DDD規則設計聚合,我們不想使用公共設定器公開內部狀態)並處理物件之間的迴圈引用。 - 我們為保單號新增“重複”欄位。 在這裡,我們告訴Marten不僅將策略號儲存為序列化JSON文件中聚合的一部分,而且還要建立單獨的列和唯一索引以加快搜索速度。 這樣做是因為我們想快速找到給定保單號的帳戶。
有關架構和對映自定義的更多詳細資訊將在本文後面提供。
讓我們看一下IDataStore
介面的實現:
public class MartenDataStore : IDataStore
{
private readonly IDocumentSession session;
public MartenDataStore(IDocumentStore documentStore)
{
session = documentStore.LightweightSession();
PolicyAccounts = new MartenPolicyAccountRepository(session);
}
public IPolicyAccountRepository PolicyAccounts { get; }
public async Task CommitChanges()
{
await session.SaveChangesAsync();
}
...
}
在建構函式中,我們開啟文件會話。 當我們將丟棄類的例項時,我們將關閉它(此處省略IDisposable實現,但是您可以在GitHub上檢查完整的程式碼)。 CommitChanges
方法使用DocumentSession
類的SaveChangesAsync
方法。 在這裡,我們使用Marten的非同步API,但是如果您願意,也可以使用同步版本。
IPolicyAccountRepository
的實現非常簡單。
public class MartenPolicyAccountRepository : IPolicyAccountRepository
{
private readonly IDocumentSession documentSession;
public MartenPolicyAccountRepository(IDocumentSession documentSession)
{
this.documentSession = documentSession;
}
public void Add(PolicyAccount policyAccount)
{
this.documentSession.Insert(policyAccount);
}
public async Task FindByNumber(string accountNumber)
{
return await this.documentSession
.Query()
.FirstOrDefaultAsync(p => p.PolicyNumber == accountNumber);
}
}
我們接受對建構函式中開啟文件會話的引用。 Add
方法使用DocumentSession
的Insert
方法將文件註冊為新的工作單元。在DataStore
上呼叫CommitChanges
時,文件將儲存在資料庫中。 CommitChanges
在基礎文件會話上呼叫SaveChanges
。
FindByNumber
更加有趣,因為它表明您可以使用LINQ來構造對資料庫中儲存的文件的查詢。在我們的例子中,這是一個非常簡單的查詢,它查詢具有給定編號的策略帳戶。我們將在本文中進一步詳細描述Marten LINQ功能。
自定義架構和對映
Marten將為要儲存到資料庫的聚合的每種.NET型別建立一個表。 Marten還將為每個表生成一個"upsert"資料庫函式。
預設情況下,表是在公共架構中建立的,並以"mt_doc_"字首和您的類名的串聯命名。
在我們的例子中,我們有PolicyAccount
類,因此Marten建立了mt_doc_policyaccount
表。
您可以使用各種自定義選項。
您可以指定要使用的資料庫架構。在我們的案例中,我們希望將所有表建立為"payment_service"模式的一部分。
var store = DocumentStore.For(_ =>
{
_.DatabaseSchemaName = "payment_service";
}
You can also specify schema for each table.
_.Storage.MappingFor(typeof(BillingPeriod)).DatabaseSchemaName = "billing";
預設情況下,Marten建立一個帶有ID列的表,資料列序列為json,並新增一些元資料列:上次修改日期,.NET型別名稱,版本(用於樂觀併發)和軟刪除標記列。
Marten要求您的類具有將被對映到主鍵的屬性。 預設情況下,Marten將查詢名為:id,Id或ID的屬性。 您可以通過使用[Identity]屬性註釋類的一個屬性來更改它,或者在文件儲存初始化程式碼中自定義對映。
var store = DocumentStore.For(_ =>
{
_.Schema.For.Identity(x => x.MyId);
}
您還可以自定義ID生成策略。 例如,您可以選擇使用CombGuid
(順序guid)。
_.Schema.For().IdStrategy(new CombGuidIdGeneration());
如果要提高查詢效能,可以命令Marten建立索引和重複欄位。
您的第一個選擇是使用計算索引。 在下面的示例中,我們在所有者的名字和姓氏上建立了索引,因此按這兩個欄位進行搜尋應該更快。
_.Schema.For().Index(x => x.Owner.FirstName);
_.Schema.For().Index(x => x.Owner.LastName);
請注意,計算索引不適用於DateTime和DateTimeOffset欄位。
第二種選擇是引入所謂的重複欄位。 我們使用這種方法通過相應的保單號優化查詢帳戶。
_.Schema.For().Duplicate(t => t.PolicyNumber,pgType: "varchar(50)", configure: idx => idx.IsUnique = true);
在這裡,我們告訴Marten為具有唯一索引的varchar(50)
型別的策略號新增附加欄位。 這樣,Marten不僅將策略號儲存為JSON資料的一部分,而且還將其儲存到具有唯一索引的單獨的列中,因此對它的搜尋應該超級快。
您可以為給定型別啟用樂觀併發。
_.Schema.For().UseOptimisticConcurrency(true);
還有許多其他選項,例如全文字索引,外來鍵可讓我們連結兩個聚合(gin / gist索引)。
儲存聚合
藉助IDataStore
和IPolicyAccountRepository
,可以輕鬆儲存PolicyAccount
聚合。
這是建立新帳戶並將其儲存在資料庫中的示例程式碼。
public async Task Handle(PolicyCreated notification, CancellationToken cancellationToken)
{
var policy = new PolicyAccount(notification.PolicyNumber, policyAccountNumberGenerator.Generate());
using (dataStore)
{
dataStore.PolicyAccounts.Add(policy);
await dataStore.CommitChanges();
}
}
如您所見,儲存和載入域物件不需要任何形式(程式碼或屬性)或配置的對映。
您的類必須滿足的唯一要求是:您的類必須可序列化為JSON(可以對JSON Serializer進行tweek配置,以使您的類正確地進行序列化/反序列化),您的類必須公開識別符號欄位或屬性。識別符號屬性將用作主鍵的值源。欄位/屬性名稱必須是ID或ID或ID,但是您可以使用[Identity]
屬性覆蓋此規則,也可以在程式碼中自定義對映。以下資料型別可用作識別符號:字串
,Guid
,CombGuid
(順序GUID),int
,long
或自定義類。對於int
和long
而言,Marten使用HiLo生成器。 Marten確保在IDocumentSession.Store
期間設定了識別符號。
Marten還支援樂觀併發。可以根據每種文件型別啟用此功能。為了為您的類啟用樂觀併發,您可以將[UseOptimisticConcurrency]
屬性新增到您的類或自定義架構配置。
載入聚合
載入聚合也是微不足道的。
public async Task Handle(GetAccountBalanceQuery request, CancellationToken cancellationToken)
{
var policyAccount = await dataStore.PolicyAccounts.FindByNumber(request.PolicyNumber);
if (policyAccount == null)
{
throw new PolicyAccountNotFound(request.PolicyNumber);
}
return new GetAccountBalanceQueryResult
{
Balance = new PolicyAccountBalanceDto
{
PolicyNumber = policyAccount.PolicyNumber,
PolicyAccountNumber = policyAccount.PolicyAccountNumber,
Balance = policyAccount.BalanceAt(DateTimeOffset.Now)
}
};
}
查詢方式
Marten提供廣泛的LINQ支援。 示例簡單查詢,查詢具有給定編號的策略的策略帳戶:
session.Query<PolicyAccount>().Where(p => p.PolicyNumber == "12121212")
結合了多個條件和邏輯運算子的查詢示例:
session.Query<PolicyAccount>().Where(p => p.PolicyNumber == "12121212" && p.PolicyAccountNumber!="32323232323")
搜尋您的彙總的子集合並查詢條目金額等於200的帳戶的示例:
var accounts = session.Query()
.Where(p => p.Entries.Any(_ => _.Amount == 200.0M)).ToList()
請注意,搜尋子集合的支援僅限於檢查子集合的成員是否相等(但是在以後的Marten版本中可能會改變)。
您還可以在物件層次結構中進行深入搜尋。 例如,如果我們將帳戶所有者資料儲存在保單帳戶中,則可以像這樣搜尋給定人員的保單帳戶:
var accounts = session.Query()
.Where(p => p.Owner.Name.LastName == “Jones” && p.Owner.Name.FirstName == “Tim”)).ToList()
您可以使用StartsWith
,EndsWith
和Contains
搜尋字串欄位。
session.Query<PolicyAccount>().Where(p => p.PolicyNumber.EndsWith("009898"))
您可以根據聚合屬性計算Count
、Min
、Max
、Average
和Sum
。
session.Query().Max(p => p.PolicyAccountNumber)
您可以訂購結果,並使用Take
/ Skip
進行分頁。
session.Query().Skip(10).Take(10).OrderBy(p => p.PolicyAccountNumber)
還有一個方便的快捷方式ToPagedList
結合了跳過和採用。
如果您無法弄清楚為什麼查詢沒有達到預期效果,Marten可以為您提供預覽LINQ查詢的功能。
var query = session.Query()
.Where(p => p.PolicyNumber == "1223");
var cmd = query.ToCommand(FetchType.FetchMany);
var sql = cmd.CommandText;
下面的程式碼片段將LINQ查詢轉換為ADO.NET命令,以便您可以檢查查詢文字和引數值。
可以在此處找到受支援的運算子的完整列表。
除了LINQ,您還可以使用SQL查詢文件。
var user =
session.Query("select data from payment_service.mt_doc_policyaccount where data ->> 'PolicyAccountNumber' = 1221212")
.Single();
您可以選擇從資料庫檢索原始JSON。
var json = session.Json.FindById<PolicyAccount>(id);
編譯查詢
也有稱為編譯查詢的高階功能。 LINQ在構造查詢時非常酷並且有用,但是它具有一定的效能和記憶體使用開銷。
如果您的查詢很複雜並且經常執行,則可以利用編譯查詢。 使用編譯的查詢,您可以避免在每次查詢執行時解析LINQ表示式樹的開銷。
編譯查詢是實現ICompiledQuery<TDoc,TResult>
介面的類。
示例查詢類,用於搜尋具有給定編號的策略帳戶。
public class FindAccountByNumberQuery : ICompiledQuery<PolicyAccount, PolicyAccount>
{
public string AccountNumber { get; set; }
public Expression<Func<IQueryable, PolicyAccount>> QueryIs()
{
return q => q.FirstOrDefault(p => p.PolicyAccountNumber == AccountNumber);
}
}
此處的關鍵方法是QueryIs
。 此方法返回定義查詢的表示式。
該類可以這樣使用:
var account = session.Query(new FindAccountByNumberQuery {AccountNumber = "11121212"});
您可以在此處閱讀有關編譯查詢的更多資訊。
修補資料
Marten修補API可用於更新資料庫中的現有文件。在某些情況下,這比將整個文件載入到記憶體,對其進行序列化,更改,反序列化然後儲存回資料庫更為有效。
修復資料錯誤和處理類結構中的更改時,修補程式API也非常有用。
我們的設計不會永遠保持不變。隨著時間的流逝,我們將向類中新增新屬性,更改對集合的簡單引用,或者相反。某些屬性可能會被提取並重構為新類,某些屬性可能會被丟棄。
使用關係資料庫中的表時,我們有一組眾所周知的SQL DDL命令,例如ALTER TABLE ADD / DROP COLUMN。
使用JSON文件時,我們必須以某種方式處理所有更改,以便在更改相應的類時仍可以載入和查詢現有文件。
讓我們嘗試修改PolicyAccount
類並遷移資料庫中的現有資料,使其保持一致。
我們從PolicyAccount
開始,必須具有代表帳戶所有者姓氏和名字的屬性。
public class PolicyAccount
{
public Guid Id { get; protected set; }
public string PolicyAccountNumber { get; protected set; }
public string PolicyNumber { get; protected set; }
public string OwnerFirstName { get; protected set; }
public string OwnerName { get; protected set; }
public ICollection Entries { get; protected set; }
…
在資料庫中,我們的資料如下所示:
{
"Id": "51d43842-896d-4d92-b1b9-b4c6512d3cf7",
"$id": "2",
"Entries": [],
"OwnerName": "Jones",
"PolicyNumber": "POLICY_1",
"OwnerFirstName": "Tim",
"PolicyAccountNumber": "231232132131"
}
我們可以看到OwnerName
不是最佳名稱,我們想將其重新命名為OwnerLastName
。
在C#上,這非常容易,因為大多數IDE都提供了開箱即用的重新命名重構功能。 進行操作,然後使用Patch API修復資料庫中的資料
public void RenameProperty()
{
using (var session = SessionProvider.OpenSession())
{
session
.Patch(x => x.OwnerLastName == null)
.Rename("OwnerName", x => x.OwnerLastName);
session.SaveChanges();
}
}
如果執行此方法,資料庫中的資料現在將如下所示:
{
"Id": "51d43842-896d-4d92-b1b9-b4c6512d3cf7",
"$id": "2",
"Entries": [],
"PolicyNumber": "POLICY_1",
"OwnerLastName": "Jones",
"OwnerFirstName": "Tim",
"PolicyAccountNumber": "231232132131"
}
讓我們嘗試一些更復雜的事情。 我們決定將OwnerFirstName
和OwnerLastName
提取到一個類中。 現在,我們的C#程式碼如下所示:
public class PolicyAccount
{
public Guid Id { get; protected set; }
public string PolicyAccountNumber { get; protected set; }
public string PolicyNumber { get; protected set; }
public string OwnerFirstName { get; protected set; }
public string OwnerLastName { get; protected set; }
public Owner Owner { get; protected set; }
public ICollection Entries { get; protected set; }
}
我們添加了一個具有FirstName
和LastName
屬性的新類。 現在,我們將使用Patch API修復資料庫中的資料。
public void AddANewProperty()
{
using (var session = SessionProvider.OpenSession())
{
session
.Patch(x=>x.Owner.LastName==null)
.Duplicate(x => x.OwnerLastName, w => w.Owner.LastName);
session
.Patch(x=>x.Owner.FirstName==null)
.Duplicate(x => x.OwnerFirstName, w => w.Owner.FirstName);
session.SaveChanges();
}
}
以及我們資料庫中的資料:
{
"Id": "51d43842-896d-4d92-b1b9-b4c6512d3cf7",
"$id": "2",
"Owner": {
"LastName": "Jones",
"FirstName": "Tim"
},
"Entries": [],
"PolicyNumber": "POLICY_1",
"OwnerLastName": "Jones",
"OwnerFirstName": "Tim",
"PolicyAccountNumber": "231232132131"
}
現在是時候清理了。 我們必須從C#程式碼和資料庫中的資料中刪除未使用的OwnerFirstName
和OwnerLastName
屬性。
public void RemoveProperty()
{
using (var session = SessionProvider.OpenSession())
{
session
.Patch(x=>x.Owner!=null)
.Delete("OwnerLastName");
session
.Patch(x=>x.Owner!=null)
.Delete("OwnerFirstName");
session.SaveChanges();
}
}
資料庫中的資料現在看起來像這樣。 OwnerFirstName
和OwnerLastName
不見了。
{
"Id": "51d43842-896d-4d92-b1b9-b4c6512d3cf7",
"$id": "2",
"Owner": {
"LastName": "Jones",
"FirstName": "Tim"
},
"Entries": [],
"PolicyNumber": "POLICY_1",
"PolicyAccountNumber": "231232132131"
}
補丁程式API提供了更多開箱即用的操作。你可以在這裡讀更多關於它的內容。
修補程式API要求您安裝PostgreSQL PLV8引擎。
除了Marten的Patching API外,您始終可以使用PostgreSQL的全部功能,該功能可為您提供一組可與JSON型別一起使用的功能,並將其與使用JavaScript作為PLV8引擎提供的資料庫功能/過程的語言相結合。實際上,Patch API生成的功能是用JavaScript編寫的,並使用PLV8引擎在資料庫中執行。
Marten利弊
優點
- 兩全其美:關係資料庫的ACID和SQL支援以及文件資料庫的易於使用和開發。
- ACID支援使您可以在一個事務中儲存來自許多不同表的許多文件,而大多數文件資料庫都不支援。
- 使用文件使您可以儲存和載入文件,而不必定義使用關係資料庫時在物件模型和資料庫模型之間定義的對映。這樣可以加快開發速度,尤其是在開發的早期階段,您不必擔心方案更改。
- 對LINQ查詢的廣泛支援為EF和NHibernate使用者提供了熟悉的體驗。
- 能夠用作文件儲存和事件儲存。
- 能夠為您的單元/整合測試快速設定/拆卸資料。
- 批量操作支援。
- 用於修補現有文件的API。
- 不能或無法執行LINQ查詢時可以使用SQL。
- 在整合測試中輕鬆使用真實的資料庫。建立一個數據庫,用初始資料填充它,然後清理它是非常簡單和快速的。
- 多租戶支援。
- 編譯和批處理查詢支援。
DocumentSession
能夠參與TransactionScope
託管的事務。
缺點
- 僅適用於PostgreSQL。
- 使用Patch API進行資料遷移需要更多的工作和學習新知識。
- 在子集合中搜索的支援有限。
- 不太適合報表和臨時查詢。
總結
在為微服務設計資料訪問策略時,有多個選項可供選擇。除實體框架或手工SQL之外,還有其他選項。
Marten是一個成熟的庫,具有許多有用的功能和良好的LINQ支援。如果您以PostgreSQL資料庫為目標,並使用域驅動的設計方法將您的域模型劃分為小的聚合,那麼Marten值得一試。
它在設計和探索階段或構建原型時也可能是非常有用的工具,使您可以快速演化域模型並能夠持久化和查詢資料。