■ 가상 액터/그레인을 사용해 멤버 클러스터에서 그레인 간 통신하는 방법을 보여준다.
▶ Messages.proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
syntax = "proto3"; option csharp_namespace = "TestProject"; message GetSmartBulbStateResponse { string state = 1; } message ChangeSmartBulbStateRequest { string smart_bulb_identity = 1; bool is_on = 2; } |
▶ Grains.proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
syntax = "proto3"; option csharp_namespace = "TestProject"; import "google/protobuf/empty.proto"; import "Messages.proto"; service SmartBulbGrain { rpc TurnOn (google.protobuf.Empty) returns (google.protobuf.Empty); rpc TurnOff (google.protobuf.Empty) returns (google.protobuf.Empty); rpc GetState (google.protobuf.Empty) returns (GetSmartBulbStateResponse); } service SmartHouseGrain { rpc ChangeSmartBulbState (ChangeSmartBulbStateRequest) returns (google.protobuf.Empty); } |
▶ SmartBulbGrain.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
using Proto; using Proto.Cluster; namespace TestProject; /// <summary> /// 스마트 전구 그레인 /// </summary> public class SmartBulbGrain : SmartBulbGrainBase { //////////////////////////////////////////////////////////////////////////////////////////////////// Enumeration ////////////////////////////////////////////////////////////////////////////////////////// Private #region 스마트 전구 상태 - SmartBulbState /// <summary> /// 스마트 전구 상태 /// </summary> private enum SmartBulbState { Unknown, On, Off } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 클러스터 ID /// </summary> private readonly ClusterIdentity clusterIdentity; /// <summary> /// 스마트 전구 상태 /// </summary> private SmartBulbState state = SmartBulbState.Unknown; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - SmartBulbGrain(context, clusterIdentity) /// <summary> /// 생성자 /// </summary> /// <param name="context">컨텍스트</param> /// <param name="clusterIdentity">클러스터 ID</param> public SmartBulbGrain(IContext context, ClusterIdentity clusterIdentity) : base(context) { this.clusterIdentity = clusterIdentity; Console.WriteLine($"스마트 전구 생성 : {this.clusterIdentity.Identity}"); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 켜기 - TurnOn() /// <summary> /// 켜기 /// </summary> /// <returns>태스크</returns> public override async Task TurnOn() { if(this.state != SmartBulbState.On) { Console.WriteLine($"스마트 전구 켜기 : {this.clusterIdentity.Identity}"); this.state = SmartBulbState.On; await NotifyHouse(); } } #endregion #region 끄기 - TurnOff() /// <summary> /// 끄기 /// </summary> /// <returns>태스크</returns> public override async Task TurnOff() { if(this.state != SmartBulbState.Off) { Console.WriteLine($"스마트 전구 끄기 : {this.clusterIdentity.Identity}"); this.state = SmartBulbState.Off; await NotifyHouse(); } } #endregion #region 상태 구하기 - GetState() /// <summary> /// 상태 구하기 /// </summary> /// <returns>스마트 전구 상태 구하기 결과</returns> public override Task<GetSmartBulbStateResponse> GetState() { return Task.FromResult(new GetSmartBulbStateResponse { State = this.state.ToString() }); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region 하우스 통지하기 - NotifyHouse() /// <summary> /// 하우스 통지하기ㄴ /// </summary> /// <returns>태스크</returns> private async Task NotifyHouse() { await Context .GetSmartHouseGrain("마이하우스") .ChangeSmartBulbState ( new ChangeSmartBulbStateRequest { SmartBulbIdentity = this.clusterIdentity.Identity, IsOn = this.state == SmartBulbState.On }, CancellationToken.None ); } #endregion } |
▶ SmartHouseGrain.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
using Proto; using Proto.Cluster; namespace TestProject; /// <summary> /// 스마트 하우스 그레인 /// </summary> public class SmartHouseGrain : SmartHouseGrainBase { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 클러스터 ID /// </summary> private readonly ClusterIdentity clusterIdentity; /// <summary> /// 켜진 스마트 전구 정렬 세트 /// </summary> private readonly SortedSet<string> turnedOnSmartBulbSortedSet = new SortedSet<string>(); #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - SmartHouseGrain(context, clusterIdentity) /// <summary> /// 생성자 /// </summary> /// <param name="context">컨텍스트</param> /// <param name="clusterIdentity">클러스터 ID</param> public SmartHouseGrain(IContext context, ClusterIdentity clusterIdentity) : base(context) { this.clusterIdentity = clusterIdentity; Console.WriteLine($"스마트 하우스 그레인 생성 : {this.clusterIdentity.Identity}"); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 스마트 전구 상태 변경하기 - ChangeSmartBulbState(request) /// <summary> /// 스마트 전구 상태 변경하기 /// </summary> /// <param name="request">스마트 전구 상태 변경 요청</param> /// <returns>태스크</returns> public override Task ChangeSmartBulbState(ChangeSmartBulbStateRequest request) { if(request.IsOn) { this.turnedOnSmartBulbSortedSet.Add(request.SmartBulbIdentity); } else { this.turnedOnSmartBulbSortedSet.Remove(request.SmartBulbIdentity); } Console.WriteLine($"{this.clusterIdentity.Identity} : {this.turnedOnSmartBulbSortedSet.Count}"); return Task.CompletedTask; } #endregion } |
▶ SmartBulbSimulator.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
using Proto; using Proto.Cluster; namespace TestProject; /// <summary> /// 스마트 전구 시뮬레이터 /// </summary> public class SmartBulbSimulator : BackgroundService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 액터 시스템 /// </summary> private readonly ActorSystem actorSystem; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - SmartBulbSimulator(actorSystem) /// <summary> /// 생성자 /// </summary> /// <param name="actorSystem">액터 시스템</param> public SmartBulbSimulator(ActorSystem actorSystem) { this.actorSystem = actorSystem; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 실행하기 (비동기) - ExecuteAsync(cancellationToken) /// <summary> /// 실행하기 (비동기) /// </summary> /// <param name="cancellationToken">취소 토큰</param> /// <returns>태스크</returns> protected override async Task ExecuteAsync(CancellationToken cancellationToken) { Random random = new Random(DateTime.Now.Millisecond); string[] lightBulbArray = new string[] { "거실1", "거실2", "침실", "부엌" }; while(!cancellationToken.IsCancellationRequested) { string id = lightBulbArray[random.Next(lightBulbArray.Length)]; SmartBulbGrainClient client = this.actorSystem .Cluster() .GetSmartBulbGrain(id); if(random.Next(2) > 0) { await client.TurnOn(cancellationToken); } else { await client.TurnOff(cancellationToken); } await Task.Delay(TimeSpan.FromMilliseconds(500), cancellationToken); } } #endregion } |
▶ ActorSystemConfiguration.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
using Proto; using Proto.Cluster; using Proto.Cluster.Partition; using Proto.Cluster.Testing; using Proto.DependencyInjection; using Proto.Remote; using Proto.Remote.GrpcNet; namespace TestProject; /// <summary> /// 액터 시스템 구성 /// </summary> public static class ActorSystemConfiguration { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 액터 시스템 추가하기 - AddActorSystem(serviceCollection) /// <summary> /// 액터 시스템 추가하기 /// </summary> /// <param name="serviceCollection">서비스 컬렉션</param> public static void AddActorSystem(this IServiceCollection serviceCollection) { serviceCollection.AddSingleton ( provider => { ActorSystemConfig actorSystemConfig = ActorSystemConfig.Setup(); GrpcNetRemoteConfig grpcNetRemoteConfig = GrpcNetRemoteConfig .BindToLocalhost() .WithProtoMessages(MessagesReflection.Descriptor); ClusterConfig clusterConfig = ClusterConfig.Setup ( clusterName : "TestProject", clusterProvider : new TestProvider(new TestProviderOptions(), new InMemAgent()), identityLookup : new PartitionIdentityLookup() ) .WithClusterKind ( kind : SmartBulbGrainActor.Kind, prop : Props.FromProducer ( () => new SmartBulbGrainActor ( (context, clusterIdentity) => new SmartBulbGrain(context, clusterIdentity) ) ) ) .WithClusterKind ( kind : SmartHouseGrainActor.Kind, prop : Props.FromProducer ( () => new SmartHouseGrainActor ( (context, clusterIdentity) => new SmartHouseGrain(context, clusterIdentity) ) ) ); return new ActorSystem(actorSystemConfig) .WithServiceProvider(provider) .WithRemote(grpcNetRemoteConfig) .WithCluster(clusterConfig); } ); } #endregion } |
▶ ActorSystemClusterHostedService.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
using Proto; using Proto.Cluster; namespace TestProject; /// <summary> /// 액터 시스템 클러스터 호스트 서비스 /// </summary> public class ActorSystemClusterHostedService : IHostedService { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 액터 시스템 /// </summary> private readonly ActorSystem actorSystem; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - ActorSystemClusterHostedService(actorSystem) /// <summary> /// 생성자 /// </summary> /// <param name="actorSystem">액터 시스템</param> public ActorSystemClusterHostedService(ActorSystem actorSystem) { this.actorSystem = actorSystem; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 시작하기 (비동기) - StartAsync(cancellationToken) /// <summary> /// 시작하기 (비동기) /// </summary> /// <param name="cancellationToken">취소 토큰</param> /// <returns>태스크</returns> public async Task StartAsync(CancellationToken cancellationToken) { Console.WriteLine("클러스터 멤버를 시작한다."); await this.actorSystem .Cluster() .StartMemberAsync(); } #endregion #region 중단하기 (비동기) - StopAsync(cancellationToken) /// <summary> /// 중단하기 (비동기) /// </summary> /// <param name="cancellationToken">취소 토큰</param> /// <returns>태스크</returns> public async Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("클러스터 멤버를 중단한다."); await this.actorSystem .Cluster() .ShutdownAsync(); } #endregion } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
using Proto; using Proto.Cluster; using TestProject; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); builder.Services.AddActorSystem(); builder.Services.AddHostedService<ActorSystemClusterHostedService>(); builder.Services.AddHostedService<SmartBulbSimulator>(); WebApplication application = builder.Build(); ILoggerFactory loggerFactory = application.Services.GetRequiredService<ILoggerFactory>(); Log.SetLoggerFactory(loggerFactory); application.MapGet("/", () => Task.FromResult("안녕하세요, Proto.Cluster!")); application.MapGet ( "/smart-bulbs/{identity}", async (ActorSystem actorSystem, string identity) => { return await actorSystem .Cluster() .GetSmartBulbGrain(identity) .GetState(CancellationToken.None); } ); application.Run(); |