.NET 云原生架构师训练营(基于 OP Storming 和 Actor 的大型分布式架构二)--学习笔记...

 点击上方“DotNet NB”关注公众号

回复“1”获取开发者路线图

0933a2d08af07d17496e7fd29ead07f0.gif

学习分享 丨作者 / 郑 子 铭    

这是DotNet NB 公众号的第202篇原创文章

目录

  • 为什么我们用 Orleans

  • Dapr VS Orleans

  • Actor 模型

  • Orleans 的核心概念

  • 结合 OP Storming 的实践

结合 OP Storming 的实践

  • 业务模型

  • 设计模型

  • 代码实现

业务模型

28b37d56e30cdc8690f2aefee06feb8e.png

我们可以把关键对象(职位、客户行为记录、线索)参考为 actor

猎头顾问一边寻找职位,一边寻找候选人,撮合之后匹配成线索,然后推荐候选人到客户公司,进行面试,发放 offer,候选人入职

设计模型

d93d353ea16a9b621646fe0c38170e68.png

我们新建职位的时候需要一个参数对象 CreateJobArgument,相当于录入数据

创建了 Job 之后,它有三个行为:浏览、点赞、投递

投递之后会直接产生一个意向的 Thread,可以继续去推进它的状态:推荐 -> 面试 -> offer -> 入职

针对浏览和点赞会产生两种不同的活动记录:ViewActivity 和 StarActivity

代码实现

  • HelloOrleans.Host

HelloOrleans.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 Include="Microsoft.Orleans.Core" Version="3.6.5" />
<PackageReference Include="Microsoft.Orleans.Server" Version="3.6.5" />
<PackageReference Include="Microsoft.Orleans.CodeGenerator.MSBuild" Version="3.6.5" />
<PackageReference Include="Microsoft.Orleans.OrleansTelemetryConsumers.Linux" Version="3.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, ILifecycleParticipant<IGrainLifecycle>
{public GrainReference GrainReference { get { return Data.GrainReference; } }/// <summary>/// String representation of grain's SiloIdentity including type and primary key./// </summary>public 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 : Grain<Job>, 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 Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />

在 Program 中需要通过 WebApplication 的 Builder 配置 Orleans

builder.Host.UseOrleans(silo =>
{silo.UseLocalhostClustering();silo.AddMemoryGrainStorage("hello-orleans");
});

在 JobGrain 中使用 hello-orleans 这个 Storage 标识一下

[StorageProvider(ProviderName = "hello-orleans")]
public class JobGrain : Grain<Job>, 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 Task<IActionResult> CreateAsync([FromBody] CreateJobViewModel model)
{var jobId = Guid.NewGuid().ToString();var jobGrain = _factory.GetGrain<IJobGrain>(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{Task<Job> 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 : Grain<Job>, IJobGrain{public async Task<Job> 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 Task<IActionResult> CreateAsync([FromBody] CreateJobViewModel model)
{var jobId = Guid.NewGuid().ToString();var jobGrain = _factory.GetGrain<IJobGrain>(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 Task<IActionResult> GetAsync(string jobId)
{var jobGrain = _factory.GetGrain<IJobGrain>(jobId);
}

这个时候我们需要去 Grain 的接口里面加一个 Get 方法

using HelloOrleans.Host.Contract.Entity;
using Orleans;namespace HelloOrleans.Host.Contract.Grain
{public interface IJobGrain : IGrainWithStringKey{Task Create(Job job);Task<Job> Get();Task View(string contactId);}
}

Get 方法是不需要传 id 的,因为这个 id 就是 Grain 的 id,你激活的时候就已经有了,直接返回 this.State

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 : Grain<Job>, IJobGrain{public async Task Create(Job job){this.State = job;await this.WriteStateAsync();}public Task<Job> 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 Task<IActionResult> GetAsync(string jobId)
{var jobGrain = _factory.GetGrain<IJobGrain>(jobId);return Ok(await jobGrain.Get());
}

这里我们可以再加点校验逻辑

[Route("{jobId}")]
[HttpGet]
public async Task<IActionResult> GetAsync(string jobId)
{if (string.IsNullOrEmpty(jobId)){throw new ArgumentNullException(nameof(jobId));}var jobGrain = _factory.GetGrain<IJobGrain>(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": "第一个职位"
}

7e8b647eb508fb6385d3f8088c6a1a6b.png

可以看到方法调用成功,返回的 job 里面包含了 identity

接着我们使用 Create 方法返回的 identity 作为入参调用 Get 方法

b99c2a078f6dec50b562570b350f403a.png

可以看到方法调用成功,返回同一个 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 交流学习群长按识别下方二维码,或点击阅读原文。和我一起,交流学习,分享心得。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/279937.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PHP 多维数组转json对象

PHP 多维数组转json对象 php 数组转json对象&#xff0c;可能大家都知道要用json_encode,但是转换出来的格式多有不同&#xff0c;此处做个小小的记录&#xff01; 1. 一维数组转json对象 <?php $arr_1 [one, two, three]; var_dump(json_encode($arr_1)); $arr_2 [0 >…

微软文本检索_如何在Microsoft Word中引用其他文档中的文本

微软文本检索You probably have some text that you type often in your Word documents, such as addresses. Instead of retyping this text every time you need it, you can put this common text into one Word document and reference it in other documents–it’ll eve…

Hadoop-Flume-类比吸尘器图解

2019独角兽企业重金招聘Python工程师标准>>> 这是我自己理解Hadoop-Flume的方式 转载于:https://my.oschina.net/u/3697442/blog/1560613

ChatGPT 之后,再玩玩 Stable-Diffusion

前些天体验的 ChatGPT 主要用来进行文本方面的处理&#xff0c;那么图片生成有没有这样的 AI 工具 呢&#xff1f;答案是肯定的。例如&#xff1a;和菜头公众号的题图和文章中的插图大多都是使用 Stable-Diffusion 的 AI 图形生成工具创作的。顺着 Stable-Diffusion 搜索了下相…

渗透测试入门DVWA 教程1:环境搭建

首先欢迎新萌入坑。哈哈。你可能抱着好奇心或者疑问。DVWA 是个啥&#xff1f; DVWA是一款渗透测试的演练系统&#xff0c;在圈子里是很出名的。如果你需要入门&#xff0c;并且找不到合适的靶机&#xff0c;那我就推荐你用DVWA。 我们通常将演练系统称为靶机&#xff0c;下面请…

移动硬盘改台式机硬盘_如何在台式机或移动设备上离线使用Google云端硬盘

移动硬盘改台式机硬盘If there’s any drawback to using cloud-based services for all your productivity and organization needs, it’s that if you can’t get an Internet connection, you’re basically out of luck. 如果使用基于云的服务来满足您的所有生产力和组织需…

你可能不知道的容器镜像安全实践

大家好&#xff0c;我是Edison。最近在公司搭建CI流水线&#xff0c;涉及到容器镜像安全的话题&#xff0c;形成了一个笔记&#xff0c;分享与你&#xff0c;也希望我们都能够提高对安全的重视。时代背景近年来应用程序逐步广泛运行在容器内&#xff0c;容器的采用率也是逐年上…

【Win 10 应用开发】UI Composition 札记(二):基本构件

在上一篇中&#xff0c;老周用一个示例&#xff0c;演示了框架视图的创建过程&#xff0c;在本篇中&#xff0c;老周将给大伙伴们说一下 Composition 构建 UI 的一些“零件”。 UI Composition 有一个核心类——对&#xff0c;就是 Compositor 类&#xff0c;它是总生产车间&am…

禁用内置键盘_如何禁用Windows 10的所有内置广告

禁用内置键盘Windows 10 has a lot of built-in advertising. This isn’t just about the free upgrade offer: Even if you purchase a new PC that comes with a Windows 10 license or spend $200 for a copy of Windows 10 Professional, you’ll see ads in your operati…

震惊!顶着 39.5℃高烧 ,我和这哥俩都聊了些啥?

这是头哥侃码的第271篇原创上周三&#xff0c;我邀请了两位嘉宾进入直播间&#xff0c;即便自己顶着 39.5 度的高烧&#xff0c;还是强打精神与这哥俩聊了俩小时。相信关注我的朋友们都知道&#xff0c;我是头哥侃码的主理人&#xff0c;同时也是上海TGO上海分会董事会成员。赵…

在Windows Mobile模拟器(Emulator)建立网络连接

因为想使用Windows Mobile Emulator进行网络通信程序的测试&#xff0c;所以找方法配置Emulator的网络连接。在网上找了一些文章&#xff0c;很多都说需要安装Virtual PC 2007. 例如下面的文章Enable Network Connection Windows Mobile 6 Emulator 如果需要 Virtual PC 2007 可…

api游戏编程鼠标选择拖动_如何选择合适的游戏鼠标

api游戏编程鼠标选择拖动You don’t need a gaming mouse to play PC games—just about any mouse with two buttons and a wheel will play anything you want it to. But that’s no reason to deny yourself the wonderful variety of gaming mouse designs on the market.…

iOS - 上架的APP 生成二维码下载

1.首先打开苹果App Store商店进入到里面&#xff0c;找到需要打开链接地址的应用程序&#xff0c;例如&#xff1a;百度。2. 在App Store商店里面先点击一下应用程序图标&#xff0c;再按一下…分享按钮。 3. 接着选择分享APP&#xff0c;再点击拷贝链接地址&#xff0c;将应用…

Rsa2加密报错java.security.spec.InvalidKeySpecException的解决办法

最近在和支付宝支付做个对接&#xff0c;Java项目中用到了RSA2进行加解密&#xff0c;在加密过程中遇到了错误&#xff1a; java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence 代码执行到这句…

浅析领域驱动设计

1.概要DDD&#xff08;Domain-driven design&#xff0c;模型驱动设计&#xff09;是一种软件设计的指导思想&#xff0c;而非固定的一套公式化开发模板&#xff08;这样就会导致网络上出现各种基于自己或业务上的理解而产出的DDD落地的实现&#xff0c;会让很想学习的开发者迷…

Delphi实现的透明阴影以及蒙版效果菜单

QQ2010的皮肤控件目前实现了一部分&#xff0c;看到有些软件的菜单&#xff0c;都有阴影&#xff0c;透明等效果&#xff0c;于是开始重新实现菜单控件&#xff0c;QQ2009版的菜单控件&#xff0c;是自己从TComponent继承了完全模拟实现的一个菜单&#xff0c;虽然实现了菜单控…

cortana搜索框_如何在Windows 10任务栏上隐藏Cortana搜索框

cortana搜索框One of the most talked about features in the latest version of Windows 10 was the Cortana personal assistant that is integrated directly into the taskbar. But what if you don’t want to waste all that taskbar space? 最新版本的Windows 10中最受…

Kotlin 基础 - 数据类型

一、Boolean 类型 Boolean 值有两个值&#xff0c;分别为 true 或 false。多数情况下&#xff0c;Kotlin 中的 Boolean 相当于 Java 中的基本类型 boolean&#xff0c;只有在必要的情况下才会装箱成为 Java 中的装箱类型 Boolean。这一切都是交由编译器来完成&#xff0c;我们无…

微软公布Entity Framework 8.0规划

微软.NET团队在博客上公布了有关 Entity Framework Core 8.0&#xff08;也称为 EF Core 8 或 EF8&#xff09;的未来规划。EF Core 8 是 EF Core 7 之后的下一个版本&#xff0c;这将是一个长期支持版本&#xff1b;计划于 2023 年 11 月与 .NET 8 同时发布。该公司表示&#…

roku能不能安装软件_如何阻止假期更改Roku主题

roku能不能安装软件Wondering why your Roku looks…different? Roku occasionally changes the background for its millions of users, something they call a “featured theme.” 想知道为什么您的Roku看起来...不同吗&#xff1f; Roku偶尔会改变其数百万用户的背景&…