窓を作っては壊していた人のブログ

この謎のブログタイトルの由来を知るものはもういないだろう

Durable FunctionsがmacOSでもローカルでEmulatorのAzuriteを使って開発できるようになってた

つい最近AzuriteにTable ServiceのPreviewが追加されたようです。

github.com

これが何を意味するかと言うと、Durable FunctionsのOrchestration関数の開始や停止などのアクションが格納、処理できるようになったため、AzuriteをバックエンドにDurable Functionsでアプリケーションを開発できるようになったってことですヤッター!

今までは例えばmacOSでDurable Functionsを使った開発をする場合は、 local.setting.json に有効なStorage Accountの接続情報を記述して、アプリケーションは手元で、各アクションの履歴はAzure上のTableにみたいな流れにすれば開発は出来ていました。 それがこれからはAzuriteをローカルで動かして、接続情報をローカルに向けるだけで開発が出来るようになります。

今まで出来なかったよねの話やIssueの話は

k-miyake.github.io

こちらの記事を見てみると良いです。

試してみる

まずは現状把握として Azure Storage Explorer でローカルに何も立っていない、アクセス出来ないよねを確認してみます。

f:id:yamachu_co:20210526174226p:plain
Docker起動前のStorageAccountの様子

Listには載っていますが、クリックしても接続エラーが出るだけです。

ということでAzure Storage Explorerから手元が見えるようにAzuriteを起動してみます。 AzuriteはnpmやDockerHub、GitHubなどで提供されています。 今回はDockerHubで提供されているlatestを使って試してみます。

$ docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite
Azurite Blob service is starting at http://0.0.0.0:10000
Azurite Blob service is successfully listening at http://0.0.0.0:10000
Azurite Queue service is starting at http://0.0.0.0:10001
Azurite Queue service is successfully listening at http://0.0.0.0:10001
Azurite Table service is starting at http://0.0.0.0:10002
Azurite Table service is successfully listening at http://0.0.0.0:10002

こんな感じで立ち上げたら10002番ポートでTable Serviceが立ち上がってるみたいですね。

この状態でAzure Storage ExplorerでRefresh Allしてみると接続出来るのが確認できると思います。 接続が確認できたら手元で適当にDurable Functionsのアプリケーションを立ててみましょう。

とりあえず手っ取り早く動かしたいので以下の物を作りました

  1. func new で JavaScript のアプリケーションを作る
  2. npm i durable-functions でDurable Functions動かせるようにしておく
  3. func new で Durable Functions orchestrator, Durable Functions activity, Durable Functions HTTP starterの3つを作る

これで最低限動くものが出来ましたが、このステップの他に必要なことがあります。 この記事の先頭で言及していた local.settings.json の書き換えです。

ローカルに接続するための接続情報を記述する必要があるので、以下のように設定します。

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
  }
}

詳しい接続文字列についてはDocsを参照してください。

ローカルでの Azure Storage の開発に Azurite エミュレーターを使用する | Microsoft Docs

この状態でスタートをしてみると

$ func start

Azure Functions Core Tools
Core Tools Version:       3.0.3477 Commit hash: 5fbb9a76fc00e4168f2cc90d6ff0afe5373afc6d  (64-bit)
Function Runtime Version: 3.0.15584.0

[2021-05-26T09:15:12.228Z] Cannot create directory for shared memory usage: /dev/shm/AzureFunctions
[2021-05-26T09:15:12.229Z] System.IO.FileSystem: Access to the path '/dev/shm/AzureFunctions' is denied. Operation not permitted.

Functions:

    DurableFunctionsHttpStart: [POST,GET] http://localhost:7071/api/orchestrators/{functionName}

    DurableFunctionsOrchestratorJS: orchestrationTrigger

    Hello: activityTrigger

待受が始まるので、HttpTriggerからOrchestratorを呼び出してみます。

$ curl http://localhost:7071/api/orchestrators/DurableFunctionsOrchestratorJS

では返ったきたStatusGetのURLを見てみましょう

$ curl http://localhost:7071/api/orchestrators/DurableFunctionsOrchestratorJS
{
  "id": "fcd4eb831b964f278f621807ebffff21",
  "statusQueryGetUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/fcd4eb831b964f278f621807ebffff21?taskHub=TestHubName&connection=Storage&code=6/k4Nwo5z8gCWp9fygP2WPAiM78w9BgmzMvxEHZa4C3CnhdXVkW2fA==",
  "sendEventPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/fcd4eb831b964f278f621807ebffff21/raiseEvent/{eventName}?taskHub=TestHubName&connection=Storage&code=6/k4Nwo5z8gCWp9fygP2WPAiM78w9BgmzMvxEHZa4C3CnhdXVkW2fA==",
  "terminatePostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/fcd4eb831b964f278f621807ebffff21/terminate?reason={text}&taskHub=TestHubName&connection=Storage&code=6/k4Nwo5z8gCWp9fygP2WPAiM78w9BgmzMvxEHZa4C3CnhdXVkW2fA==",
  "rewindPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/fcd4eb831b964f278f621807ebffff21/rewind?reason={text}&taskHub=TestHubName&connection=Storage&code=6/k4Nwo5z8gCWp9fygP2WPAiM78w9BgmzMvxEHZa4C3CnhdXVkW2fA==",
  "purgeHistoryDeleteUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/fcd4eb831b964f278f621807ebffff21?taskHub=TestHubName&connection=Storage&code=6/k4Nwo5z8gCWp9fygP2WPAiM78w9BgmzMvxEHZa4C3CnhdXVkW2fA=="
}%
#
$ curl "http://localhost:7071/runtime/webhooks/durabletask/instances/fcd4eb831b964f278f621807ebffff21?taskHub=TestHubName&connection=Storage&code=6/k4Nwo5z8gCWp9fygP2WPAiM78w9BgmzMvxEHZa4C3CnhdXVkW2fA=="
{"name":"DurableFunctionsOrchestratorJS","instanceId":"fcd4eb831b964f278f621807ebffff21","runtimeStatus":"Completed","input":null,"customStatus":null,"output":["Hello Tokyo!","Hello Seattle!","Hello London!"],"createdTime":"2021-05-26T09:23:44Z","lastUpdatedTime":"2021-05-26T09:23:44Z"}%

動作が確認できました。

またAzure Storage Explorerを確かめてみると

f:id:yamachu_co:20210526182643p:plain
Orchestration関数を実行した後の様子

Dockerに立てたEmulatorが使われていることが確認できました、素晴らしい。

この様にAzuriteのアップデートによりmacOSでもローカル環境でDurable Functionsのストレス無い開発が出来るようになりました。 Dev環境として一個アカウントを払い出すのが面倒…けれども自分のマシンはmacだし…みたいな悩みを抱えていた人にとっては素晴らしいアップデートだと感じています。

.NET5でWPFなアプリケーションをPublishTrimmedしようとしたらTrimしすぎたのでIgnoreしていい感じにする

メモです。

最近DMM版ウマ娘の画面サイズ自動変更ツールのUIをWPF化しようとしていた時に起こった事象。

.NET Core 3.1からアプリケーションをpublishする時にリンクしているアセンブリの不使用な部分をパッケージから削除する機能が追加されています。

docs.microsoft.com

これを使用してWPFなアプリケーションのTrimを行ったところ、アプリケーションが実行時にクラッシュしてしまいました。 イベントビューアでエラーを確認したところ

Application: UmaMadoManager.Windows.exe
CoreCLR Version: 5.0.521.16609
.NET Version: 5.0.5
Description: The process was terminated due to an unhandled exception.
Exception Info: System.TypeInitializationException: The type initializer for 'System.Windows.FrameworkElement' threw an exception.
 ---> System.TypeInitializationException: The type initializer for 'System.Windows.Documents.TextElement' threw an exception.
 ---> System.TypeInitializationException: The type initializer for 'System.Windows.Documents.Typography' threw an exception.
 ---> System.IO.FileNotFoundException: 
File name: 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at System.Windows.Media.TextFormatting.TextRunTypographyProperties.OnPropertiesChanged()
   at MS.Internal.Text.TypographyProperties.ResetProperties()
   at MS.Internal.Text.TypographyProperties..ctor()
   at System.Windows.Documents.Typography..cctor()
   --- End of inner exception stack trace ---
   at System.Windows.Documents.TextElement..cctor()
   --- End of inner exception stack trace ---
   at System.Windows.FrameworkElement..cctor()
   --- End of inner exception stack trace ---
   at System.Runtime.CompilerServices.RuntimeHelpers._RunClassConstructor(RuntimeType type)
   at System.Windows.DependencyProperty.FromName(String name, Type ownerType)
   at System.Windows.Baml2006.WpfXamlType.FindDependencyPropertyBackedProperty(String name, Boolean isAttachable, Boolean skipReadOnlyCheck)
   at System.Windows.Baml2006.WpfXamlType.FindMember(String name, Boolean isAttached, Boolean skipReadOnlyCheck)
   at System.Windows.Baml2006.WpfXamlType.LookupMember(String name, Boolean skipReadOnlyCheck)
   at System.Xaml.XamlType.GetMember(String name)
   at System.Windows.Baml2006.Baml2006SchemaContext.GetProperty(Int16 propertyId, XamlType parentType)
   at System.Windows.Baml2006.Baml2006Reader.Process_PropertyWithStaticResourceId()
   at System.Windows.Baml2006.Baml2006Reader.Process_OneBamlRecord()
   at System.Windows.Baml2006.Baml2006Reader.ReadObject(KeyRecord record)
   at System.Windows.ResourceDictionary.CreateObject(KeyRecord key)
   at System.Windows.ResourceDictionary.OnGettingValue(Object key, Object& value, Boolean& canCache)
   at System.Windows.ResourceDictionary.OnGettingValuePrivate(Object key, Object& value, Boolean& canCache)
   at System.Windows.ResourceDictionary.GetValueWithoutLock(Object key, Boolean& canCache)
   at System.Windows.ResourceDictionary.GetValue(Object key, Boolean& canCache)
   at System.Windows.ResourceDictionary.GetValueWithoutLock(Object key, Boolean& canCache)
   at System.Windows.ResourceDictionary.GetValue(Object key, Boolean& canCache)
   at System.Windows.ResourceDictionary.get_Item(Object key)
   at System.Windows.Application.FindResource(Object resourceKey)
   at UmaMadoManager.Windows.App.OnStartup(StartupEventArgs e)
   at System.Windows.Application.<.ctor>b__1_0(Object unused)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.WrappedInvoke(Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority priority, Delegate method, Object arg)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at UmaMadoManager.Windows.App.Main()

以下のようなエラーが発生していました。

Exceptionでぐぐってみたら

github.com

こんなIssueが引っかかった。

Trimming default WPF app breaks it · Issue #14261 · dotnet/sdk · GitHub

ここらへんで詳しい原因を述べていているで気になる方はこちらをチェック。

とりあえずWorkaroundとして載せられている

<ItemGroup>
    <TrimmerRootAssembly Include="System.Runtime"/>
    <TrimmerRootAssembly Include="System.Runtime.Extensions"/>
    <TrimmerRootAssembly Include="System.Diagnostics.Debug"/>
</ItemGroup>

こいつをcsprojに追記してとりあえず動作するようになった。 やっぱり動作するかのテスト追加しないとだなー