您好,登錄后才能下訂單哦!
這篇文章主要講解了“通過lms.samples熟悉lms微服務框架的使用解析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“通過lms.samples熟悉lms微服務框架的使用解析”吧!
lms.sample項目由三個獨立的微服務應用模塊組成:account、stock、order和一個網關項目gateway構成。
每個獨立的微服務應用采用模塊化設計,主要由如下幾部分組成:
主機(Host): 主要用于托管微服務應用本身,主機通過引用應用服務項目(應用接口的實現),托管微服務應用,通過托管應用服務,在主機啟動的過程中,向服務注冊中心注冊服務路由。
應用接口層(Application.Contracts): 用于定義應用服務接口,通過應用接口,該微服務模塊與其他微服務模塊或是網關進行rpc通信的能力。在該項目中,除了定義應用服務接口之前,一般還定義與該應用接口相關的DTO
對象。應用接口除了被該微服務應用項目引用,并實現應用服務之前,還可以被網關或是其他微服務模塊引用。網關或是其他微服務項目通過應用接口生成的代理與該微服務模塊通過rpc進行通信。
應用服務層(Application): 應用服務是該微服務定義的應用接口的實現。應用服務與DDD傳統分層架構的應用層的概念一致。主要負責外部通信與領域層之間的協調。一般地,應用服務進行業務流程控制,但是不包含業務邏輯的實現。
領域層(Domain): 負責表達業務概念,業務狀態信息以及業務規則,是該微服務模塊的業務核心。一般地,在該層可以定義聚合根、實體、領域服務等對象。
領域共享層(Domain.Shared): 該層用于定義與領域對象相關的模型、實體等相關類型。不包含任何業務實現,可以被其他微服務引用。
數據訪問(DataAccess)層: 該層一般用于封裝數據訪問相關的對象。例如:倉庫對象、 SqlHelper
、或是ORM相關的類型等。在lms.samples中,通過efcore實現數據的讀寫操作。
lms框架不允許服務外部與微服務主機直接通信,應用請求必須通過http請求到達網關,網關通過lms提供的中間件解析到服務條目,并通過rpc與集群內部的微服務進行通信。所以,如果服務需要與集群外部進行通信,那么,開發者定義的網關必須要引用各個微服務模塊的應用接口層;以及必須要使用lms相關的中間件。
.net版本: 5.0.101
lms版本: 1.0.0
IDE: (1) visual studio 最新版 (2) Rider(推薦)
通過lms框架創建一個業務模塊非常方便,只需要通過如下4個步驟,就可以輕松的創建一個lms應用業務模塊。
1.創建項目
創建控制臺應用(Console Application)項目,并且引用Silky.Lms.NormHost
包。
dotnet add package Silky.Lms.NormHost --version 1.0.0
2.應用程序入口與主機構建
在main
方法中,通用.net的主機Host
構建并注冊lms微服務。在注冊lms微服務時,需要指定lms啟動的依賴模塊。
一般地,如果開發者不需要額外依賴其他模塊,也無需在應用啟動或停止時執行方法,那么您可以直接指定NormHostModule
模塊。
public class Program { public static async Task Main(string[] args) { await CreateHostBuilder(args).Build().RunAsync(); } private static IHostBuilder CreateHostBuilder(string[] args) { return Host.CreateDefaultBuilder(args) .RegisterLmsServices<NormHostModule>() ; } }
3.配置文件
lms框架支持yml
或是json
格式作為配置文件。通過appsettings.yml
對lms框架進行統一配置,通過appsettings.${Environment}.yml
對不同環境變量下的配置項進行設置。
開發者如果直接通過項目的方式啟動應用,那么可以通過Properties/launchSettings.json
的environmentVariables.DOTNET_ENVIRONMENT
環境變量。如果通過docker-compose
的方式啟動應用,那么可以通過.env
設置DOTNET_ENVIRONMENT
環境變量。
為保證配置文件有效,開發者需要顯式的將配置文件拷貝到項目生成目錄下。
4.引用應用服務層和數據訪問層
一般地,主機項目需要引用該微服務模塊的應用服務層和數據訪問層。只有主機引用應用服務層,主機在啟動時,才會生成服務條目的路由,并且將服務路由注冊到服務注冊中心。
一個典型的主機項目文件如下所示:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> </PropertyGroup> <ItemGroup> <PackageReference Include="Silky.Lms.NormHost" Version="$(LmsVersion)" /> </ItemGroup> <ItemGroup> <None Update="appsettings.yml"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> <None Update="appsettings.Production.yml"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> <None Update="appsettings.Development.yml"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Lms.Account.Application\Lms.Account.Application.csproj" /> <ProjectReference Include="..\Lms.Account.EntityFrameworkCore\Lms.Account.EntityFrameworkCore.csproj" /> </ItemGroup> </Project>
一般地,一個微服務模塊的主機必須要配置:服務注冊中心、分布式鎖鏈接、分布式緩存地址、集群rpc通信token、數據庫鏈接地址等。
如果使用docker-compose來啟動和調試應用的話,那么,rpc配置節點下的的host和port可以缺省,因為生成的每個容器的都有自己的地址和端口號。
如果直接通過項目的方式啟動和調試應用的話,那么,必須要配置rpc節點下的port,每個微服務模塊的主機應用有自己的端口號。
lms框架的必要配置如下所示:
rpc: host: 0.0.0.0 rpcPort: 2201 token: ypjdYOzNd4FwENJiEARMLWwK0v7QUHPW registrycenter: connectionStrings: 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183;127.0.0.1:2184,127.0.0.1:2185,127.0.0.1:2186 # 使用分號;來區分不同的服務注冊中心 registryCenterType: Zookeeper distributedCache: redis: isEnabled: true configuration: 127.0.0.1:6379,defaultDatabase=0 lock: lockRedisConnection: 127.0.0.1:6379,defaultDatabase=1 connectionStrings: default: server=127.0.0.1;port=3306;database=account;uid=root;pwd=qwe!P4ss;
一般地,在應用接口層開發者需要安裝Silky.Lms.Rpc
包。如果該微服務模塊還涉及到分布式事務,那么還需要安裝Silky.Lms.Transaction.Tcc
,當然,您也可以選擇在應用接口層安裝Silky.Lms.Transaction
包,在應用服務層安裝Silky.Lms.Transaction.Tcc
包。
開發者只需要在應用接口通過ServiceRouteAttribute
特性對應用接口進行直接即可。
Lms約定應用接口應當以IXxxAppService
命名,這樣,服務條目生成的路由則會以api/xxx
形式生成。當然這并不是強制的。
每個應用接口的方法都對應著一個服務條目,服務條目的Id為: 方法的完全限定名 + 參數名
您可以在應用接口層對方法的緩存、路由、服務治理、分布式事務進行相關配置。該部分內容請參考官方文檔
網關或是其他模塊的微服務項目需要引用服務應用接口項目或是通過nuget的方式安裝服務應用接口生成的包。
[Governance(ProhibitExtranet = true)]
可以標識一個方法禁止與集群外部進行通信,通過網關也不會生成swagger文檔。
應用接口方法生成的WebApi支持restful API風格。Lms支持通過方法的約定命名生成對應http方法請求的WebApi。您當然開發者也可以通過HttpMethodAttribute
特性對某個方法進行注解。
一個典型的應用接口的定義
/// <summary> /// 賬號服務 /// </summary> [ServiceRoute] public interface IAccountAppService { /// <summary> /// 新增賬號 /// </summary> /// <param name="input">賬號信息</param> /// <returns></returns> Task<GetAccountOutput> Create(CreateAccountInput input); /// <summary> /// 通過賬號名稱獲取賬號 /// </summary> /// <param name="name">賬號名稱</param> /// <returns></returns> [GetCachingIntercept("Account:Name:{0}")] [HttpGet("{name:string}")] Task<GetAccountOutput> GetAccountByName([CacheKey(0)] string name); /// <summary> /// 通過Id獲取賬號信息 /// </summary> /// <param name="id">賬號Id</param> /// <returns></returns> [GetCachingIntercept("Account:Id:{0}")] [HttpGet("{id:long}")] Task<GetAccountOutput> GetAccountById([CacheKey(0)] long id); /// <summary> /// 更新賬號信息 /// </summary> /// <param name="input"></param> /// <returns></returns> [UpdateCachingIntercept( "Account:Id:{0}")] Task<GetAccountOutput> Update(UpdateAccountInput input); /// <summary> /// 刪除賬號信息 /// </summary> /// <param name="id">賬號Id</param> /// <returns></returns> [RemoveCachingIntercept("GetAccountOutput","Account:Id:{0}")] [HttpDelete("{id:long}")] Task Delete([CacheKey(0)]long id); /// <summary> /// 訂單扣款 /// </summary> /// <param name="input"></param> /// <returns></returns> [Governance(ProhibitExtranet = true)] [RemoveCachingIntercept("GetAccountOutput","Account:Id:{0}")] [Transaction] Task<long?> DeductBalance(DeductBalanceInput input); }
應用服務層只需要引用應用服務接口層以及領域服務層,并實現應用接口相關的方法。
確保該微服務模塊的主機引用了該模塊的應用服務層,這樣主機才能夠托管該應用本身。
應用服務層可以通過引用其他微服務模塊的應用接口層項目(或是安裝nuget包,取決于開發團隊的項目管理方法),與其他微服務模塊進行rpc通信。
應用服務層需要依賴領域服務,通過調用領域服務的相關接口,實現該模塊的核心業務邏輯。
DTO到實體對象或是實體對DTO對象的映射關系可以在該層指定映射關系。
一個典型的應用服務的實現如下所示:
public class AccountAppService : IAccountAppService { private readonly IAccountDomainService _accountDomainService; public AccountAppService(IAccountDomainService accountDomainService) { _accountDomainService = accountDomainService; } public async Task<GetAccountOutput> Create(CreateAccountInput input) { var account = input.MapTo<Domain.Accounts.Account>(); account = await _accountDomainService.Create(account); return account.MapTo<GetAccountOutput>(); } public async Task<GetAccountOutput> GetAccountByName(string name) { var account = await _accountDomainService.GetAccountByName(name); return account.MapTo<GetAccountOutput>(); } public async Task<GetAccountOutput> GetAccountById(long id) { var account = await _accountDomainService.GetAccountById(id); return account.MapTo<GetAccountOutput>(); } public async Task<GetAccountOutput> Update(UpdateAccountInput input) { var account = await _accountDomainService.Update(input); return account.MapTo<GetAccountOutput>(); } public Task Delete(long id) { return _accountDomainService.Delete(id); } [TccTransaction(ConfirmMethod = "DeductBalanceConfirm", CancelMethod = "DeductBalanceCancel")] public async Task<long?> DeductBalance(DeductBalanceInput input) { var account = await _accountDomainService.GetAccountById(input.AccountId); if (input.OrderBalance > account.Balance) { throw new BusinessException("賬號余額不足"); } return await _accountDomainService.DeductBalance(input, TccMethodType.Try); } public Task DeductBalanceConfirm(DeductBalanceInput input) { return _accountDomainService.DeductBalance(input, TccMethodType.Confirm); } public Task DeductBalanceCancel(DeductBalanceInput input) { return _accountDomainService.DeductBalance(input, TccMethodType.Cancel); } }
領域層是該微服務模塊核心業務處理的模塊,一般用于定于聚合根、實體、領域服務、倉儲等業務對象。
領域層引用該微服務模塊的應用接口層,方便使用dto對象。
領域層可以通過引用其他微服務模塊的應用接口層項目(或是安裝nuget包,取決于開發團隊的項目管理方法),與其他微服務模塊進行rpc通信。
領域服務必須要直接或間接繼承ITransientDependency
接口,這樣,該領域服務才會被注入到ioc容器。
lms.samples 項目使用TanvirArjel.EFCore.GenericRepository包實現數據的讀寫操作。
一個典型的領域服務的實現如下所示:
public class AccountDomainService : IAccountDomainService { private readonly IRepository _repository; private readonly IDistributedCache<GetAccountOutput, string> _accountCache; public AccountDomainService(IRepository repository, IDistributedCache<GetAccountOutput, string> accountCache) { _repository = repository; _accountCache = accountCache; } public async Task<Account> Create(Account account) { var exsitAccountCount = await _repository.GetCountAsync<Account>(p => p.Name == account.Name); if (exsitAccountCount > 0) { throw new BusinessException($"已經存在{account.Name}名稱的賬號"); } exsitAccountCount = await _repository.GetCountAsync<Account>(p => p.Email == account.Email); if (exsitAccountCount > 0) { throw new BusinessException($"已經存在{account.Email}Email的賬號"); } await _repository.InsertAsync<Account>(account); return account; } public async Task<Account> GetAccountByName(string name) { var accountEntry = _repository.GetQueryable<Account>().FirstOrDefault(p => p.Name == name); if (accountEntry == null) { throw new BusinessException($"不存在名稱為{name}的賬號"); } return accountEntry; } public async Task<Account> GetAccountById(long id) { var accountEntry = _repository.GetQueryable<Account>().FirstOrDefault(p => p.Id == id); if (accountEntry == null) { throw new BusinessException($"不存在Id為{id}的賬號"); } return accountEntry; } public async Task<Account> Update(UpdateAccountInput input) { var account = await GetAccountById(input.Id); if (!account.Email.Equals(input.Email)) { var exsitAccountCount = await _repository.GetCountAsync<Account>(p => p.Email == input.Email); if (exsitAccountCount > 0) { throw new BusinessException($"系統中已經存在Email為{input.Email}的賬號"); } } if (!account.Name.Equals(input.Name)) { var exsitAccountCount = await _repository.GetCountAsync<Account>(p => p.Name == input.Name); if (exsitAccountCount > 0) { throw new BusinessException($"系統中已經存在Name為{input.Name}的賬號"); } } await _accountCache.RemoveAsync($"Account:Name:{account.Name}"); account = input.MapTo(account); await _repository.UpdateAsync(account); return account; } public async Task Delete(long id) { var account = await GetAccountById(id); await _accountCache.RemoveAsync($"Account:Name:{account.Name}"); await _repository.DeleteAsync(account); } public async Task<long?> DeductBalance(DeductBalanceInput input, TccMethodType tccMethodType) { var account = await GetAccountById(input.AccountId); var trans = await _repository.BeginTransactionAsync(); BalanceRecord balanceRecord = null; switch (tccMethodType) { case TccMethodType.Try: account.Balance -= input.OrderBalance; account.LockBalance += input.OrderBalance; balanceRecord = new BalanceRecord() { OrderBalance = input.OrderBalance, OrderId = input.OrderId, PayStatus = PayStatus.NoPay }; await _repository.InsertAsync(balanceRecord); RpcContext.GetContext().SetAttachment("balanceRecordId",balanceRecord.Id); break; case TccMethodType.Confirm: account.LockBalance -= input.OrderBalance; var balanceRecordId1 = RpcContext.GetContext().GetAttachment("orderBalanceId")?.To<long>(); if (balanceRecordId1.HasValue) { balanceRecord = await _repository.GetByIdAsync<BalanceRecord>(balanceRecordId1.Value); balanceRecord.PayStatus = PayStatus.Payed; await _repository.UpdateAsync(balanceRecord); } break; case TccMethodType.Cancel: account.Balance += input.OrderBalance; account.LockBalance -= input.OrderBalance; var balanceRecordId2 = RpcContext.GetContext().GetAttachment("orderBalanceId")?.To<long>(); if (balanceRecordId2.HasValue) { balanceRecord = await _repository.GetByIdAsync<BalanceRecord>(balanceRecordId2.Value); balanceRecord.PayStatus = PayStatus.Cancel; await _repository.UpdateAsync(balanceRecord); } break; } await _repository.UpdateAsync(account); await trans.CommitAsync(); await _accountCache.RemoveAsync($"Account:Name:{account.Name}"); return balanceRecord?.Id; } }
lms.samples項目使用orm框架efcore進行數據讀寫。
lms提供了IConfigureService
,通過繼承該接口即可使用IServiceCollection
的實例指定數據上下文對象和注冊倉庫服務。
public class EfCoreConfigureService : IConfigureService { public void ConfigureServices(IServiceCollection services, IConfiguration configuration) { services.AddDbContext<OrderDbContext>(opt => opt.UseMySql(configuration.GetConnectionString("Default"), ServerVersion.AutoDetect(configuration.GetConnectionString("Default")))) .AddGenericRepository<OrderDbContext>(ServiceLifetime.Transient) ; } public int Order { get; } = 1; }
3.主機項目需要顯式的引用該項目,只有這樣,該項目的ConfigureServices
才會被調用。
4.數據遷移,請參考
1.使用git 克隆lms項目源代碼,lms.samples存放在samples
目錄下
# github git clone https://github.com/liuhll/lms.git # gitee git clone https://gitee.com/liuhll2/lms.git
服務注冊中心zookeeper
緩存服務redis
mysql數據庫
如果您電腦已經安裝了docker以及docker-compose命令,那么您只需要進入samples\docker-compose\infrastr
目錄下,打開命令行工作,執行如下命令就可以自動安裝zookeeper
、redis
、mysql
等服務:
docker-compose -f .\docker-compose.mysql.yml -f .\docker-compose.redis.yml -f .\docker-compose.zookeeper.yml up -d
需要分別進入到各個微服務模塊下的EntityFrameworkCore
項目(例如:),執行如下命令:
dotnet ef database update
例如: 需要遷移account模塊的數據庫如下所示:
order模塊和stock模塊與account模塊一致,在服務運行前都需要通過數據庫遷移命令生成相關數據庫。
數據庫遷移指定數據庫連接地址默認指定的是appsettings.Development.yml
中配置的,您可以通過修改該配置文件中的connectionStrings.default
配置項來指定自己的數據庫服務地址。
如果沒有dotnet ef
命令,則需要通過dotnet tool install --global dotnet-ef
安裝ef工具,請[參考] (https://docs.microsoft.com/zh-cn/ef/core/get-started/overview/install)
使用visual studio作為開發工具
進入到samples目錄下,使用visual studio打開lms.samples.sln
解決方案,將項目設置為多啟動項目,并將網關和各個模塊的微服務主機設置為啟動項目,如下圖:
設置完成后直接啟動即可。
使用rider作為開發工具進入到samples目錄下,使用rider打開lms.samples.sln
解決方案,打開各個微服務模塊下的Properties/launchSettings.json
,點擊圖中綠色的箭頭即可啟動項目。
啟動網關項目后,可以看到應用接口的服務條目生成的swagger api文檔 http://localhost:5000/swagger。
默認的環境變量為: Development
,如果需要修改環境變量的話,可以通過Properties/launchSettings.json
下的environmentVariables
節點修改相關環境變量,請參考在 ASP.NET Core 中使用多個環境。
數據庫連接、服務注冊中心地址、以及redis緩存地址和分布式鎖連接等配置項可以通過修改appsettings.Development.yml
配置項自定義指定。
進入到samples目錄下,使用visual studio打開lms.samples.dockercompose.sln
解決方案,將docker-compose設置為啟動項目,即可啟動和調式。
應用啟動成功后,打開: http://127.0.0.1/swagger,即可看到swagger api文檔
以docker-compose的方式啟動和調試,則指定的環境變量為:ContainerDev
數據庫連接、服務注冊中心地址、以及redis緩存地址和分布式鎖連接等配置項可以通過修改appsettings.ContainerDev.yml
配置項自定義指定,配置的服務連接地址不允許為: 127.0.0.1
或是localhost
服務啟動成功后,您可以通過寫入/api/account-post
接口和/api/product-post
新增賬號和產品,然后通過/api/order-post
接口進行測試和調式。
github: https://github.com/liuhll/lms
gitee: https://gitee.com/liuhll2/lms
感謝各位的閱讀,以上就是“通過lms.samples熟悉lms微服務框架的使用解析”的內容了,經過本文的學習后,相信大家對通過lms.samples熟悉lms微服務框架的使用解析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。