個人的なメモ

Tomohiro Suzuki @hiro128_777 のブログです。Microsoft MVP for Developer Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

「山東探究塾II中間発表会」に研究助言者として参加してきました。

2023年7月18日に、母校である山形県立山形東高等学校の「山東探究塾II中間発表会」に研究助言者として参加してきました。
 
山東探究塾とは「主体的・協働的で深い学び(探究型学習)による様々な教育プログラムを通して、将来、地域や国内外の課題を解決できる資質・能力の育成を目指す取り組み」です。
 
「山東探究塾II」は高校2年生の生徒さんの取り組みになります。
 
今回は「中間発表会」として、各チームの研究のテーマとそのテーマを選択した背景、研究の進捗状況などを発表する会でした。
 
私はITの専門家ですので、ITをテーマとした取り組みを拝見させていただきました。
 
ITの専門家は何人かいらっしゃいましたが、アカデミック領域の方が多く、ビジネス領域の方は少なかっため、参加できて多少は貢献できたのではないかと考えています。
 
まず、驚かされたのが、デジタルネイティブ世代の今の高校生の方々はごく自然にITの技術に慣れ親しんでいることです。利用者としてスマートフォンやタブレットを使いこなすことができるのは当然なので驚きはなかったのですが、開発者目線として、PC、スマートフォン、クラウド、プログラミング言語、Bluetoothなどが実現できることを理解しており、何を組み合わせればご自身の研究テーマが実現できるかということの大筋は皆さんすでに目処が立っている状況に驚きました。
 
そして、IT技術への理解があるからこそ、生徒さんの設定したテーマも簡単に解決方法が見つかるようなものにとどまらず、すでに世の中に存在するアプリの問題点を深掘りして新たなアプリ作成に挑戦するようなものなど、難易度が高いものが多く見られました。
 
そして「イノベーター」「アーリーアダプター」「キャズム理論」など、大人が業務で使用するような用語が普通に飛び交い目指すゴールが非常に高いことに驚かされました。
 
一方で、ITの実装は基本的に構想時には見えなかった問題点が多く発生するものですので、理想が高いが故に理想通りに進めてしまうと、学校の授業の一環として行うにはあまりにボリュームが大きくなってしまうことが予想されるテーマも見られました。

そういう意味で、探究すべきことの絞り込みを行い、絞り込んだ内容について解像度高く、深く探究することで、成果を出しやすくすることをお勧めしました。

また、実装方式について生徒さん自身で全てを調査するのはあまりに難易度が高い内容がありましたので、いくつかのヒントとなるキーワードをお伝えし、領域を絞って調査できるようなフィードバックをいたしました。

なお、これまでも探究塾にご参加経験のある研究助言者の方々からはかなりレベルの高い厳しめのご意見がたくさん飛び交い、探究塾の本気の度合いと厳しさを目の当たりにしました。

私自身が高校生の時にはこのような探究型の授業はまだ行われておらず、そもそも探究塾のような深い思考をする経験自体がありませんでしたので、教育のレベルが非常に上がっていることを実感するとともに、社会が高校生の皆さんに求めるレベルが格段に上がっており、「今の高校生の生徒さんは本当には高いレベルの取り組みをされているのだな、自分が高校生だった時こんなレベルの高いことが果たしてできただろうか」と思わずにはいられませんでした。

印象的だったのが、先生方が繰り返しおっしゃっていた「技術の進歩によって定型的な仕事はどんどんコンピューターに置き換えられていくため、自分で課題を設定し、仮説を立て検証し、それに基づいて新たな価値を想像する非定型の仕事ができることが社会で求められる」という言葉でした。

これは、私が日々の仕事の中で考えていることそのものであり、今のIT業界を含めた社会全体の課題について危機感を持ち、教育を実践なさってことに感銘を受けました。

私自身としては、今後も探究塾に継続的に貢献したいと考え、プログラムの実装、クラウドの利用方法、設計などについて何か質問があるようでしたら、生徒さんのご質問を継続的にお受けすることをお伝えしました。

冬には成果発表会が開催予定とのことでしたので、生徒さんたちが探究塾を通じて困難な課題に取り組んだ結果を拝見できる機会を頂けることに感謝するとともに、自分自身のモチベーションも高まりましたので、非常に素晴らしい経験をさせていただきました。

最後に、山形県立山形東高等学校の生徒さんおよび先生方にあらためて感謝を申し上げます。ありがとうございました。

Xamarin から .NET へのアップグレード

公式でも、Xamarin から .NET へのアップグレードが案内されるようになりました。いよいよ Xamarin が終焉が近くなってきました。
 
learn.microsoft.com
 
Xamarin.Forms から MAUI への移行は .NET Upgrade Assistant が利用できます。
learn.microsoft.com
 
Xamarin.Android、Xamarin.iOS、Xamarin.Mac といった Xamarin プロジェクトは現状、「手動」でのアップグレードになってしまいます。
learn.microsoft.com
 
 

Orleans の構成要素:サイロ

目次

 
前の記事はこちらです。
hiro128.hatenablog.jp
 

サイロの役割

 

 

  • サイロは複数の仮想アクターつまりグレインをホストするコンテナです。サイロは単一ではその効力を発揮できません。複数のサイロによって構成されるグループを一緒に実行しクラスターを形成することでスケーラビリティや耐障害性を提供する役割を果たします。
  • サイロのクラスターにより、アプリケーションの状態は物理的に暗黙的・透過的に分割され、負荷に応じて並列に処理が実行されますが、開発者はそれを意識することはなく Orleans を利用するだけでスケーラビリティを確保することができます。
  • サイロのクラスターにより、分割されたアプリケーションの状態のバックアップを使用することで、回復力が向上し、障害からの回復性が高まります。

 
 

サイロの実装例

 
サイロの実装では以下を行います。

  • IP、ポート番号などネットワーク関連の設定
  • クラスタの設定、ロギングの設定

 
サイロのアプリケーションコードはデプロイするインスタンスが単一であっても複数(N個)であってもコード自体は同一になります。
(スケールアウト、スケールインへの対応)

 

using System.Net;
using Orleans.Configuration;
using OrleansPoc;

var siloHost = await StartSiloAsync();

// Silo 動作確認用
var app = WebApplication.Create();
app.MapGet("/", () => $"Silo is activated {DateTime.Now}");
await app.RunAsync();

static async Task<IHost> StartSiloAsync()
{
    var builder = Host.CreateDefaultBuilder()
        .UseOrleans(
            (context, builder) =>
            {
                var siloIPAddress = IPAddress.Parse(context.Configuration["WEBSITE_PRIVATE_IP"] ?? "");

                // WEBSITE_PRIVATE_PORTS は再起動で変更されるのでWEBSITE_PRIVATE_PORTS として取得し Parse すること
                var strPorts = (context.Configuration["WEBSITE_PRIVATE_PORTS"] ?? "").Split(',');
                if (strPorts.Length < 2)
                    throw new Exception("Insufficient private ports configured.");
                var (siloPort, gatewayPort) = (int.Parse(strPorts[0]), int.Parse(strPorts[1]));
                var connectionString =
                    context.Configuration["ORLEANS_AZURE_STORAGE_CONNECTION_STRING"];

                builder
                    .ConfigureEndpoints(siloIPAddress, siloPort, gatewayPort)
                    .Configure<ClusterOptions>(
                        options =>
                        {
                            options.ClusterId = "PoCCluster";
                            options.ServiceId = "OrleansPoC";
                        })
                    .UseAzureStorageClustering(
                    options => options.ConfigureTableServiceClient(connectionString))
                    .AddAzureTableGrainStorage(
                        "PocStore",
                     options => options.ConfigureTableServiceClient(connectionString)
                     )
                    .ConfigureLogging(logging => logging.AddConsole());
            });

    var host = builder.Build();
    await host.StartAsync();

    return host;
}

 
 
次の記事はこちらです。
hiro128.hatenablog.jp
 
 

Orleans の構成要素:グレイン

グレインの役割

 

 
グレインはユーザー定義のID(identity)、業務ロジック(behavior)、およびインメモリの状態(state)をカプセル化し、他のグレインとメッセージを送受信したり、クライアントからの要求に対するレスポンスを返します。
 
 

グレインの取得

 
グレインの物理的な場所に関して開発者は意識不要です。
同一インターフェースで指定したグレインのプロキシオブジェクトを同一の GrainID で取得すると、どのフロントエンド(クライアント)から呼び出したとしても同一のグレインの実体が呼び出されます。
 

 
 

グレインの実装例

 

インターフェース

using System.Threading.Tasks;
using Orleans;

namespace OrleansPoc
{
    public interface IWriteLargeData : Orleans.IGrainWithGuidKey
    {
        Task<string> WriteLargeData();
    }
}
|cs|<
 
** 実装
>|cs|
using Grains.Status;
using Microsoft.Extensions.Logging;
using OrleansPoc.Sevices;
using System.Reflection.Emit;
using System.Threading.Tasks;
using Orleans.Runtime;

namespace OrleansPoc
{
    public class WriteLargeDataGrain : Orleans.Grain, IWriteLargeData
    {
        private readonly ILogger _logger;
        private readonly IPersistentState<WriteLargeDataState> _largeData;

        public WriteLargeDataGrain(
            [PersistentState("largeData", "PocStore")] IPersistentState<WriteLargeDataState> largeData,
            ILogger<HelloGrain> logger
            )
        {
            _largeData = largeData;
            _logger = logger;
        }

        async Task<string> IWriteLargeData.WriteLargeData()
        {
            _logger.LogInformation($"\n{DateTime.Now} WriteLargeData recieved.");

            var largeData = "";
            largeData = Enumerable.Range(0, 30000).Select(i => "ABCDEFGHIJKLMNOPQRSTUVWXYZ").Aggregate((x, y) => $"{x},{y}");

            _largeData.State.LargeData = largeData;
            await _largeData.WriteStateAsync();

            return "Grain Status の書き込みが終了しました。";
        }

    }
}

 
 

グレインのその他の特長

  • グレインはシングルスレッド実行が保証され、ロックや一貫性の問題の考慮が不要です。
  • 個々のグレインは Orleans のランタイムによって、開発者は意識することなく、必要に応じてページイン(アクティブ化)・ページアウト(非アクティブ化)されるため、メモリの使用量やグレインのロードバランシングは動的に最適化されます。
  • GrainID によって呼び出されるグレインの参照(プロキシオブジェクト)は、グレインの論理的なアイデンティティのみを含んでいます。アプリケーションコードはグレインの物理的な位置を意識することなくグレインと通信できます。グレインの物理的な位置は、Orleans のランタイムによって、障害やリソース管理、または呼び出された時点での当該のグレインが非アクティブになっているなどの理由で、時間の経過とともに変化する可能性がありますが、それを開発者がアプリケーションコードによって管理する必要はなく、意識する必要もありません。

 
 
次の記事はこちらです。
hiro128.hatenablog.jp
 
 

Orleans の構成要素:フロントエンド

フロントエンドの役割

 

フロントエンドは、www と Orleans の世界のゲートウェイの役割を果たします。
( REST API と Orleans 独自の世界を相互変換する境界)

公開されている REST API への HTTP リクエストを受信して、リクエストを処理すべき Grain に対してメソッドをコールし、 Grain からレスポンスを受け取り、 HTTP リクエストにレスポンスを返します。

なお、 フロントエンドは Orleans クラスターから見ればクライアントでもあります。
 
 

フロントエンドの実装例

 
Minimum なフロントエンドの実装としては、ASP.NET Core Minimal Web API のプロジェクトで REST API を定義し、Generic Host で Orleans クラアントを起動し、Grain プロキシオブジェクトのメソッドをコールし、戻り値を返します。
 

using Orleans.Configuration;
using OrleansPoc;

var host = await StartClientAsync();
var client = host.Services.GetRequiredService<IClusterClient>();
var hello = client.GetGrain<IHello>(Guid.NewGuid());
var writeLargeData = client.GetGrain<IWriteLargeData>(Guid.NewGuid());

var app = WebApplication.Create();
app.MapGet("/", async () => await hello.Call());
app.MapGet("/hello", async () => await hello.SayHello($"Konnichiwa!!"));
app.MapGet("/deactivate", async () => await hello.Deactivate());
app.MapGet("/writelargedata", async () => await writeLargeData.WriteLargeData());
await app.RunAsync();

static async Task<IHost> StartClientAsync()
{
    var connectionString = Environment.GetEnvironmentVariable("ORLEANS_AZURE_STORAGE_CONNECTION_STRING");

    var builder = new HostBuilder()
        .UseOrleansClient(client =>
        {
            client
                .UseAzureStorageClustering(
                    options => options.ConfigureTableServiceClient(connectionString))
                .Configure<ClusterOptions>(options =>
                {
                    options.ClusterId = "PoCCluster";
                    options.ServiceId = "OrleansPoC";
                });
        })
        .ConfigureLogging(logging => logging.AddConsole());

    var host = builder.Build();
    await host.StartAsync();

    return host;
}

 
 
次の記事はこちらです。
hiro128.hatenablog.jp