时间:2021-05-25
经过一段时间的开发与测试,终于发布了Lms框架的第一个正式版本(1.0.0版本),并给出了lms框架的样例项目lms.samples。本文通过对lms.samples的介绍,简述如何通过lms框架快速的构建一个微服务的业务框架,并进行应用开发。
lms.sample项目由三个独立的微服务应用模块组成:account、stock、order和一个网关项目gateway构成。
每个独立的微服务应用采用模块化设计,主要由如下几部分组成:
lms框架不允许服务外部与微服务主机直接通信,应用请求必须通过http请求到达网关,网关通过lms提供的中间件解析到服务条目,并通过rpc与集群内部的微服务进行通信。所以,如果服务需要与集群外部进行通信,那么,开发者定义的网关必须要引用各个微服务模块的应用接口层;以及必须要使用lms相关的中间件。
通过lms框架创建一个业务模块非常方便,只需要通过如下4个步骤,就可以轻松的创建一个lms应用业务模块。
1.创建项目
创建控制台应用(Console Application)项目,并且引用Silky.Lms.NormHost包。
dotnet add package Silky.Lms.NormHost --version 1.0.02.应用程序入口与主机构建
在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: ypjdYOzNd4FwENJiEARMLWwK0v7QUHPWregistrycenter: 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: ZookeeperdistributedCache: redis: isEnabled: true configuration: 127.0.0.1:6379,defaultDatabase=0lock: lockRedisConnection: 127.0.0.1:6379,defaultDatabase=1connectionStrings: 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包。
一个典型的应用接口的定义
/// <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); }一个典型的应用服务的实现如下所示:
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); } }一个典型的领域服务的实现如下所示:
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; } }3.主机项目需要显式的引用该项目,只有这样,该项目的ConfigureServices才会被调用。
4.数据迁移,请参考
1.使用git 克隆lms项目源代码,lms.samples存放在samples目录下
# githubgit clone https://github.com/liuhll/lms.git# giteegit clone https://gitee.com/liuhll2/lms.git如果您电脑已经安装了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模块一致,在服务运行前都需要通过数据库迁移命令生成相关数据库。
使用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微服务框架内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
JSP开发中Apache-HTTPClient用户验证的实例详解前言:在微服务框架之外的系统中,我们经常会遇到使用httpClient进行接口调用的问题,除了进
在一个Cisco交换网络中间,已知某台机器的IP地址,如何找出它连接到了哪台交换机的哪个端口上呢?最方便快捷的方法使使用CiscoWorks2000LMS网管软
SpringCloudStream官方定义SpringCloudStream是一个构建消息驱动微服务的框架。应用通过inputs和outputs来与Spring
在上篇文章[基于.netcore微服务的另类实现]结尾处,提到了如何方便自动的生成微服务的客户端代理,使对于调用方透明,同时将枯燥的东西使用框架集成,以提高使用
前言SpringCloud是基于SpringBoot的一整套实现微服务的框架。他提供了微服务开发所需的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全