富阳做网站的,网站里面的导航图标怎么做的,wordpress 虎嗅 小兽,网站建设 中小企业▲ 点击上方“DotNet NB”关注公众号回复“1”获取开发者路线图学习分享 丨作者 / 郑 子 铭 这是DotNet NB 公众号的第202篇原创文章目录为什么我们用 OrleansDapr VS OrleansActor 模型Orleans 的核心概念结合 OP Storming 的实践结合 OP Storming 的实践业务模型设计模型代…▲ 点击上方“DotNet NB”关注公众号回复“1”获取开发者路线图学习分享 丨作者 / 郑 子 铭 这是DotNet NB 公众号的第202篇原创文章目录为什么我们用 OrleansDapr VS OrleansActor 模型Orleans 的核心概念结合 OP Storming 的实践结合 OP Storming 的实践业务模型设计模型代码实现业务模型我们可以把关键对象职位、客户行为记录、线索参考为 actor猎头顾问一边寻找职位一边寻找候选人撮合之后匹配成线索然后推荐候选人到客户公司进行面试发放 offer候选人入职设计模型我们新建职位的时候需要一个参数对象 CreateJobArgument相当于录入数据创建了 Job 之后它有三个行为浏览、点赞、投递投递之后会直接产生一个意向的 Thread可以继续去推进它的状态推荐 - 面试 - offer - 入职针对浏览和点赞会产生两种不同的活动记录ViewActivity 和 StarActivity代码实现HelloOrleans.HostHelloOrleans.Host新建一个空白解决方案 HelloOrleans创建一个 ASP .NET Core 空项目 HelloOrleans.Host分别创建 BaseEntity、Job、Thread、Activity 实体namespace HelloOrleans.Host.Contract.Entity
{public class BaseEntity{public string Identity { get; set; }}
}namespace HelloOrleans.Host.Contract.Entity
{public class Job : BaseEntity{public string Title { get; set; }public string Description { get; set; }public string Location { get; set; }}
}namespace HelloOrleans.Host.Contract.Entity
{public class Thread : BaseEntity{public string JobId { get; set; }public string ContactId { get; set; }public EnumThreadStatus Status { get; set; }}
}namespace HelloOrleans.Host.Contract
{public enum EnumThreadStatus : int{Recommend,Interview,Offer,Onboard,}
}namespace HelloOrleans.Host.Contract.Entity
{public class Activity : BaseEntity{public string JobId { get; set; }public string ContactId { get; set; }public EnumActivityType Type { get; set; }}
}namespace HelloOrleans.Host.Contract
{public enum EnumActivityType : int{View 1,Star 2,}
}给 Job 添加 View 和 Star 的行为public async Task View(string contactId)
{}public async Task Star(string contactId)
{}这里就只差 Grain 的 identity我们添加 Orleans 的 nuget 包PackageReference IncludeMicrosoft.Orleans.Core Version3.6.5 /
PackageReference IncludeMicrosoft.Orleans.Server Version3.6.5 /
PackageReference IncludeMicrosoft.Orleans.CodeGenerator.MSBuild Version3.6.5 /
PackageReference IncludeMicrosoft.Orleans.OrleansTelemetryConsumers.Linux Version3.6.5 /Microsoft.Orleans.Core 是核心Microsoft.Orleans.Server 做 Host 就需要用到它Microsoft.Orleans.CodeGenerator.MSBuild 会在编译的时候帮我们生成客户端或者访问代码Microsoft.Orleans.OrleansTelemetryConsumers.Linux 是监控安装完后我们就可以继承 Grain 的基类了using Orleans;namespace HelloOrleans.Host.Contract.Entity
{public class Job : Grain{public string Title { get; set; }public string Description { get; set; }public string Location { get; set; }public async Task View(string contactId){}public async Task Star(string contactId){}}
}如果我们需要用它来做持久化是有问题的因为持久化的时候它会序列化我们所有的公有属性然而在 Grain 里面会有一些公有属性你没有办法给它序列化所以持久化的时候会遇到一些问题除非我们把持久化的东西重新写一遍public abstract class Grain : IAddressable, ILifecycleParticipantIGrainLifecycle
{public GrainReference GrainReference { get { return Data.GrainReference; } }/// summary/// String representation of grains SiloIdentity including type and primary key./// /summarypublic string IdentityString{get { return Identity?.IdentityString ?? string.Empty; }}...
}理论上你的状态和行为是可以封装在一起的这样更符合 OO 的逻辑我们现在需要分开状态和行为定义一个 IJobGrain 接口继承 IGrainWithStringKey用 string 作为它的 identity 的类型using Orleans;namespace HelloOrleans.Host.Contract.Grain
{public interface IJobGrain : IGrainWithStringKey{Task View(string contactId);}
}定义 JobGrain 继承 Grain实现 IJobGrain 接口using HelloOrleans.Host.Contract.Entity;
using HelloOrleans.Host.Contract.Grain;
using Orleans;namespace HelloOrleans.Host.Grain
{public class JobGrain : GrainJob, IJobGrain{public Task View(string contactId){throw new NotImplementedException();}}
}这是使用 DDD 来做的区分开状态和行为变成贫血模型是不得已而为之因为持久化的问题在 Orleans 的角度而言它的 Actor 绑定了一个外部的状态但是实际上我们更希望它们两在一起它的实体就变成这样namespace HelloOrleans.Host.Contract.Entity
{public class Job{public string Title { get; set; }public string Description { get; set; }public string Location { get; set; }}
}Job 不是 Actor 实例JobGrain 才是 Actor 实例接下来我们需要做一个 Host 让它跑起来添加 nuget 包PackageReference IncludeMicrosoft.Extensions.Hosting.Abstractions Version6.0.0 /在 Program 中需要通过 WebApplication 的 Builder 配置 Orleansbuilder.Host.UseOrleans(silo
{silo.UseLocalhostClustering();silo.AddMemoryGrainStorage(hello-orleans);
});在 JobGrain 中使用 hello-orleans 这个 Storage 标识一下[StorageProvider(ProviderName hello-orleans)]
public class JobGrain : GrainJob, IJobGrain添加 JobController这属于前面讲的 silo 内模式可以直接使用 IGrainFactory因为这是在同一个项目里using Microsoft.AspNetCore.Mvc;
using Orleans;namespace HelloOrleans.Host.Controllers
{[Route(job)]public class JobController : Controller{private IGrainFactory _factory;public JobController(IGrainFactory grainFactory){_factory grainFactory;}}
}添加一个创建方法 CreateAsync它的入参叫做 CreateJobViewModel包含我们需要的 Job 的数据[Route()]
[HttpPost]
public async TaskIActionResult CreateAsync([FromBody] CreateJobViewModel model)
{var jobId Guid.NewGuid().ToString();var jobGrain _factory.GetGrainIJobGrain(jobId);
}创建的时候 Grain 是不存在的必须有 identity不然 Actor 获取不到所以需要先 new 一个 identity就是 jobId通过 IGrainFactory 获取到 jobGrain 之后我们是无法获取到它的 state只能看到它的行为所以我们需要在 Grain 里面添加一个 Create 的方法方便我们调用using HelloOrleans.Host.Contract.Entity;
using Orleans;namespace HelloOrleans.Host.Contract.Grain
{public interface IJobGrain : IGrainWithStringKey{TaskJob Create(Job job);Task View(string contactId);}
}所以这个 Create 方法并不是真正的 Create只是用来设置 state 的对象再通过 WriteStateAsync 方法保存using HelloOrleans.Host.Contract.Entity;
using HelloOrleans.Host.Contract.Grain;
using Orleans;
using Orleans.Providers;namespace HelloOrleans.Host.Grain
{[StorageProvider(ProviderName hello-orleans)]public class JobGrain : GrainJob, IJobGrain{public async TaskJob Create(Job job){job.Identity this.GetPrimaryKeyString();this.State job;await this.WriteStateAsync();return this.State;}public Task View(string contactId){throw new NotImplementedException();}}
}new 一个 job调用 Create 方法设置 State得到一个带 identity 的 job然后返回 OK[Route()]
[HttpPost]
public async TaskIActionResult CreateAsync([FromBody] CreateJobViewModel model)
{var jobId Guid.NewGuid().ToString();var jobGrain _factory.GetGrainIJobGrain(jobId);var job new Job(){Title model.Title,Description model.Description,Location model.Location,};job await jobGrain.Create(job);return Ok(job);
}因为我们现在采用的是内存级别的 GrainStorage所以我们没有办法去查看它我们再加一个 Get 的方法去查询它[Route({jobId})]
[HttpGet]
public async TaskIActionResult GetAsync(string jobId)
{var jobGrain _factory.GetGrainIJobGrain(jobId);
}这个时候我们需要去 Grain 的接口里面加一个 Get 方法using HelloOrleans.Host.Contract.Entity;
using Orleans;namespace HelloOrleans.Host.Contract.Grain
{public interface IJobGrain : IGrainWithStringKey{Task Create(Job job);TaskJob Get();Task View(string contactId);}
}Get 方法是不需要传 id 的因为这个 id 就是 Grain 的 id你激活的时候就已经有了直接返回 this.Stateusing HelloOrleans.Host.Contract.Entity;
using HelloOrleans.Host.Contract.Grain;
using Orleans;
using Orleans.Providers;namespace HelloOrleans.Host.Grain
{[StorageProvider(ProviderName hello-orleans)]public class JobGrain : GrainJob, IJobGrain{public async Task Create(Job job){this.State job;await this.WriteStateAsync();}public TaskJob Get(){return Task.FromResult(this.State);}public Task View(string contactId){throw new NotImplementedException();}}
}这个地方所有你的行为都不是直接去查数据库而是利用这个 State它不需要你自己去读取跟 DDD 的 repository 不同直接通过 Grain 的 Get 方法获取 Job 返回 OK[Route({jobId})]
[HttpGet]
public async TaskIActionResult GetAsync(string jobId)
{var jobGrain _factory.GetGrainIJobGrain(jobId);return Ok(await jobGrain.Get());
}这里我们可以再加点校验逻辑[Route({jobId})]
[HttpGet]
public async TaskIActionResult GetAsync(string jobId)
{if (string.IsNullOrEmpty(jobId)){throw new ArgumentNullException(nameof(jobId));}var jobGrain _factory.GetGrainIJobGrain(jobId);return Ok(await jobGrain.Get());
}要注意如果你传入的 jobId 是不存在的因为不管你传什么只要是一个合法的字符串并且不重复它都会帮你去激活只不过在于它是否做持久化而已如果你随便传了一个 jobId这个时候不是调了 Get 方法它可能也会返回给你一个空的 state所以这个 jobId 没有这种很强的合法性的约束在调 Get 的时候要特别的注意不管是 Create 还是 Get其实都是调用了 GetGrain传了一个 identity 进去这样的一个行为在 Program 中添加 Controller 的配置using Orleans.Hosting;var builder WebApplication.CreateBuilder(args);builder.Host.UseOrleans(silo
{silo.UseLocalhostClustering();silo.AddMemoryGrainStorage(hello-orleans);
});
builder.Services.AddControllers();var app builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints
{endpoints.MapControllers();
});app.MapGet(/, () Hello World!);app.Run();我们启动项目测试一下Create 方法入参{title: 第一个职位,description: 第一个职位
}可以看到方法调用成功返回的 job 里面包含了 identity接着我们使用 Create 方法返回的 identity 作为入参调用 Get 方法可以看到方法调用成功返回同一个 job这种基于内存的存储就很适合用来做单元测试推荐阅读
.NET周报【12月第1期 2022-12-08】.NET 7 新增的 IParsable 接口介绍.NET 云原生架构师训练营基于 OP Storming 和 Actor 的大型分布式架构一--学习笔记一个.NetCore前后端分离、模块化、插件式的通用框架.NET 为什么推荐Kestrel作为网络开发框架用最少的代码打造一个Mini版的gRPC框架
点击下方卡片关注DotNet NB
一起交流学习▲ 点击上方卡片关注DotNet NB一起交流学习请在公众号后台
回复 【路线图】获取.NET 2021开发者路线图
回复 【原创内容】获取公众号原创内容
回复 【峰会视频】获取.NET Conf开发者大会视频
回复 【个人简介】获取作者个人简介
回复 【年终总结】获取作者年终总结
回复 【加群】加入DotNet NB 交流学习群长按识别下方二维码或点击阅读原文。和我一起交流学习分享心得。