.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.pswp.cn/news/279937.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/279937.shtml
英文地址,請注明出處:http://en.pswp.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

BZOJ4327:[JSOI2012]玄武密碼(SAM)

Description 在美麗的玄武湖畔&#xff0c;雞鳴寺邊&#xff0c;雞籠山前&#xff0c;有一塊富饒而秀美的土地&#xff0c;人們喚作進香河。相傳一日&#xff0c;一縷紫氣從天而至&#xff0c;只一瞬間便消失在了進香河中。老人們說&#xff0c;這是玄武神靈將天書藏匿在此。 很…

ChatGPT 之后,再玩玩 Stable-Diffusion

前些天體驗的 ChatGPT 主要用來進行文本方面的處理&#xff0c;那么圖片生成有沒有這樣的 AI 工具 呢&#xff1f;答案是肯定的。例如&#xff1a;和菜頭公眾號的題圖和文章中的插圖大多都是使用 Stable-Diffusion 的 AI 圖形生成工具創作的。順著 Stable-Diffusion 搜索了下相…

滲透測試入門DVWA 教程1:環境搭建

首先歡迎新萌入坑。哈哈。你可能抱著好奇心或者疑問。DVWA 是個啥&#xff1f; DVWA是一款滲透測試的演練系統&#xff0c;在圈子里是很出名的。如果你需要入門&#xff0c;并且找不到合適的靶機&#xff0c;那我就推薦你用DVWA。 我們通常將演練系統稱為靶機&#xff0c;下面請…

指派問題(匈牙利算法)

問題描述&#xff1a; 在生活中經常遇到這樣的問題&#xff0c;某單位需完成n項任務&#xff0c;恰好有n個人可承擔這些任務。由于每人的專長不同&#xff0c;各人完成任務不同(或所費時間)&#xff0c;效率也不同。于是產生應指派哪個人去完成哪項任務&#xff0c;使完成n項任…

移動硬盤改臺式機硬盤_如何在臺式機或移動設備上離線使用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;容器的采用率也是逐年上…

從零基礎到拿到網易Java實習offer,談談我的學習經驗

微信公眾號【程序員江湖】作者黃小斜&#xff0c;斜杠青年&#xff0c;某985碩士&#xff0c;阿里研發工程師&#xff0c;于2018 年秋招拿到 BAT 頭條、網易、滴滴等 8 個大廠 offer個人擅長領域 &#xff1a;自學編程、技術校園招聘、軟件工程考研&#xff08;關注公眾號后回復…

【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…

zbb20180710 maven Failed to read artifact descriptor--maven

Failed to read artifact descriptor--maven2016年09月10日 13:30:46閱讀數&#xff1a;13036在開發的過程中,作為新手,經常遇到Maven下載依賴的時候,"Failed to read artifact descriptor for xxx:jar"的錯誤對于這種非業務相關的問題,耽誤時間非常不效率,看到網站很…

震驚!頂著 39.5℃高燒 ,我和這哥倆都聊了些啥?

這是頭哥侃碼的第271篇原創上周三&#xff0c;我邀請了兩位嘉賓進入直播間&#xff0c;即便自己頂著 39.5 度的高燒&#xff0c;還是強打精神與這哥倆聊了倆小時。相信關注我的朋友們都知道&#xff0c;我是頭哥侃碼的主理人&#xff0c;同時也是上海TGO上海分會董事會成員。趙…

CAS原理分析及ABA問題詳解

什么是CAS CAS即Compare And Swap的縮寫&#xff0c;翻譯成中文就是比較并交換&#xff0c;其作用是讓CPU比較內存中某個值是否和預期的值相同&#xff0c;如果相同則將這個值更新為新值&#xff0c;不相同則不做更新&#xff0c;也就是CAS是原子性的操作(讀和寫兩者同時具有原…

在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;會讓很想學習的開發者迷…