agentsclimarketplace

Vb di patterns

Skill oreguchi/vb-winforms-skills/skills/vb-di-patterns

Claude Code skills for building and modernizing VB.NET Windows Forms applications on modern .NET (8 / 9)

Install
npx -y skills add oreguchi/vb-winforms-skills --skill vb-di-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

IServiceCollection拡張メソッドを使用してDI登録を整理する。関連するサービスをコンポーザブルなAdd*メソッドにグループ化し、Program.vbをクリーンに保つとともに、テストで設定を再利用可能にする。VB.NET構文では<Extension>属性とModuleを使用して拡張メソッドを定義する。

SKILL.md

13.0 KB, ~3.5k tokens by cl100k_base, as published. Nobody here has run it

依存性注入(DI)パターン

このスキルを使う場面

次の場面で使用する。

  • ASP.NET CoreアプリケーションでサービスのDI登録を整理したい
  • 数百行の登録コードで肥大化したProgram.vbやStartup.vbを解消したい
  • 本番環境とテストでサービス設定を再利用したい
  • Microsoft.Extensions.DependencyInjectionと統合するライブラリを設計したい

参考ファイル

  • advanced-patterns.md: DI拡張メソッドを使ったテスト、Akka.NETアクタースコープ管理、条件付き登録・ファクトリ登録・キー付き登録パターン

問題

整理しないと、Program.vbは管理不能になる。

' BAD: 200行以上の無秩序な登録
Dim builder = WebApplication.CreateBuilder(args)

builder.Services.AddScoped(Of IUserRepository, UserRepository)()
builder.Services.AddScoped(Of IOrderRepository, OrderRepository)()
builder.Services.AddScoped(Of IProductRepository, ProductRepository)()
builder.Services.AddScoped(Of IUserService, UserService)()
' ... さらに150行 ...

問題点:関連する登録を見つけにくい、境界が不明瞭、テストで再利用できない、マージコンフリクトが発生しやすい。


解決策:拡張メソッドによるコンポジション

関連する登録を拡張メソッドにグループ化する。

' GOOD: クリーンでコンポーザブルなProgram.vb
Dim builder = WebApplication.CreateBuilder(args)

builder.Services _
    .AddUserServices() _
    .AddOrderServices() _
    .AddEmailServices() _
    .AddPaymentServices() _
    .AddValidators()

Dim app = builder.Build()

拡張メソッドパターン

基本構造

Imports System.Runtime.CompilerServices
Imports Microsoft.Extensions.DependencyInjection

Namespace MyApp.Users

    Public Module UserServiceCollectionExtensions
        <Extension>
        Public Function AddUserServices(services As IServiceCollection) As IServiceCollection
            services.AddScoped(Of IUserRepository, UserRepository)()
            services.AddScoped(Of IUserReadStore, UserReadStore)()
            services.AddScoped(Of IUserWriteStore, UserWriteStore)()
            services.AddScoped(Of IUserService, UserService)()
            services.AddScoped(Of IUserValidationService, UserValidationService)()

            Return services
        End Function
    End Module

End Namespace

設定パラメータあり

Imports System.Runtime.CompilerServices
Imports Microsoft.Extensions.DependencyInjection

Namespace MyApp.Email

    Public Module EmailServiceCollectionExtensions
        <Extension>
        Public Function AddEmailServices(
            services As IServiceCollection,
            Optional configSectionName As String = "EmailSettings") As IServiceCollection

            ' VB.NET における OptionsBuilder の起動時検証
            ' ---
            ' 単純な ASP.NET Core / Hosting 環境では、以下の従来チェーンで動作する:
            '
            '   services.AddOptions(Of EmailOptions)() _
            '       .BindConfiguration(configSectionName) _
            '       .ValidateDataAnnotations() _
            '       .ValidateOnStart()
            '
            ' ただし VB.NET + Akka.Hosting 共存時は BC30521(オーバーロード解決失敗)が
            ' 発生することがある。Microsoft.Extensions.Options と Microsoft.Extensions.Hosting
            ' の両方が OptionsBuilderExtensions を拡張しており、VB.NET の厳格な
            ' オーバーロード解決では同名拡張メソッドの衝突と判定されるため。
            ' C# では発生しない(緩い解決規則のため)。
            '
            ' 回避策として、Microsoft.Extensions.Hosting が提供する
            ' `AddOptionsWithValidateOnStart(Of T)()` を使えばチェーン末尾の
            ' `.ValidateOnStart()` を省略できる。こちらは .NET 8 以降で利用可能。
            services.AddOptionsWithValidateOnStart(Of EmailOptions)() _
                .BindConfiguration(configSectionName) _
                .ValidateDataAnnotations()

            services.AddSingleton(Of IMjmlTemplateRenderer, MjmlTemplateRenderer)()
            services.AddSingleton(Of IEmailLinkGenerator, EmailLinkGenerator)()
            services.AddScoped(Of IUserEmailComposer, UserEmailComposer)()
            services.AddScoped(Of IEmailSender, SmtpEmailSender)()

            Return services
        End Function
    End Module

End Namespace

ファイル構成

拡張メソッドは、登録するサービスの近くに配置する。

src/
  MyApp.Api/
    Program.vb                           # 全Add*メソッドを組み合わせる
  MyApp.Users/
    Services/
      UserService.vb
    UserServiceCollectionExtensions.vb   # AddUserServices()
  MyApp.Orders/
    OrderServiceCollectionExtensions.vb  # AddOrderServices()
  MyApp.Email/
    EmailServiceCollectionExtensions.vb  # AddEmailServices()

命名規則: {Feature}ServiceCollectionExtensions.vb をそのフィーチャーのサービスと同じ場所に置く。


命名規則

パターン用途
Add{Feature}Services()汎用フィーチャー登録
Add{Feature}()明確な場合の短縮形
Configure{Feature}()主にオプション設定を行う場合
Use{Feature}()ミドルウェア(IApplicationBuilder用)

テストでの利点

Add* パターンを使うと本番の設定をテストで再利用でき、差異のある部分だけ上書きできる。WebApplicationFactory、Akka.Hosting.TestKit、スタンドアロンのServiceCollectionと組み合わせて使用できる。完全なテスト例についてはadvanced-patterns.mdの該当セクションを参照。


階層的拡張メソッド

大規模なアプリケーションでは、拡張メソッドを階層的に組み合わせる。

Imports Microsoft.Extensions.DependencyInjection
Imports System.Runtime.CompilerServices

Public Module AppServiceCollectionExtensions
    <Extension>
    Public Function AddAppServices(services As IServiceCollection) As IServiceCollection
        Return services _
            .AddDomainServices() _
            .AddInfrastructureServices() _
            .AddApiServices()
    End Function
End Module

Public Module DomainServiceCollectionExtensions
    <Extension>
    Public Function AddDomainServices(services As IServiceCollection) As IServiceCollection
        Return services _
            .AddUserServices() _
            .AddOrderServices() _
            .AddProductServices()
    End Function
End Module

Akka.Hosting統合

同じパターンはAkka.NETアクター設定にも使用できる。

Public Module OrderActorExtensions
    <Extension>
    Public Function AddOrderActors(builder As AkkaConfigurationBuilder) As AkkaConfigurationBuilder
        Return builder _
            .WithActors(Sub(system, registry, resolver)
                Dim orderProps = resolver.Props(Of OrderActor)()
                Dim orderRef = system.ActorOf(orderProps, "orders")
                registry.Register(Of OrderActor)(orderRef)
            End Sub)
    End Function
End Module

' Program.vbでの使用例
builder.Services.AddAkka("MySystem", Sub(akkaBuilder, sp)
    akkaBuilder _
        .AddOrderActors() _
        .AddInventoryActors() _
        .AddNotificationActors()
End Sub)

完全なAkka.Hostingパターンはakka-hosting-actor-patternsスキルを参照。


アンチパターン

NG:すべてをProgram.vbに登録する

' BAD: 200行以上の登録コードが並ぶ肥大化したProgram.vb

NG:過度に汎用的な拡張メソッドを作る

' BAD: 名前が曖昧で何が登録されるか伝わらない
<Extension>
Public Function AddServices(services As IServiceCollection) As IServiceCollection
    ' ...
End Function

NG:重要な設定を隠蔽する

' BAD: 設定が埋もれている
<Extension>
Public Function AddDatabase(services As IServiceCollection) As IServiceCollection
    services.AddDbContext(Of AppDbContext)(Sub(options)
        options.UseSqlServer("hardcoded-connection-string")  ' 隠蔽!
    End Sub)
    Return services
End Function

' GOOD: 設定を明示的に受け取る
<Extension>
Public Function AddDatabase(
    services As IServiceCollection,
    connectionString As String) As IServiceCollection

    services.AddDbContext(Of AppDbContext)(Sub(options)
        options.UseSqlServer(connectionString)
    End Sub)
    Return services
End Function

ベストプラクティス一覧

プラクティス効果
関連サービスをAdd*メソッドにグループ化するProgram.vbをクリーンに保ち、境界を明確にする
拡張メソッドを登録するサービスの近くに置く見つけやすく保守しやすい
IServiceCollectionを返してチェーン可能にするFluentなAPI
設定パラメータを受け取る柔軟性の確保
命名規則を統一する(Add{Feature}Services発見しやすさの向上
テストで本番の拡張メソッドを再利用する信頼性向上、重複削減

ライフタイム管理

ライフタイム使用場面
シングルトン(Singleton)ステートレス、スレッドセーフ、生成コストが高い設定、HttpClientファクトリ、キャッシュ
スコープ(Scoped)リクエストごとのステート、データベースコンテキストDbContext、リポジトリ、ユーザーコンテキスト
一時(Transient)軽量、ステートフル、生成コストが低いバリデータ、短命ヘルパー
' SINGLETON: ステートレスサービス、安全に共有
services.AddSingleton(Of IMjmlTemplateRenderer, MjmlTemplateRenderer)()

' SCOPED: データベースアクセス、リクエストごとのステート
services.AddScoped(Of IUserRepository, UserRepository)()

' TRANSIENT: 安価で短命
services.AddTransient(Of CreateUserRequestValidator)()

スコープサービスはスコープを必要とする。 ASP.NET CoreはHTTPリクエストごとにスコープを作成する。バックグラウンドサービスやアクター内では手動でスコープを作成する必要がある。

アクタースコープ管理パターンはadvanced-patterns.mdを参照。


よくある間違い

スコープサービスをシングルトンに注入する

' BAD: シングルトンがスコープサービスをキャプチャ — 古くなったDbContext!
Public Class CacheService  ' シングルトンとして登録
    Private ReadOnly _repo As IUserRepository  ' スコープ — 起動時にキャプチャされる!
End Class

' GOOD: IServiceProviderを注入し、操作ごとにスコープを作成する
Public Class CacheService
    Private ReadOnly _serviceProvider As IServiceProvider

    Public Async Function GetUserAsync(id As String) As Task(Of User)
        Using scope = _serviceProvider.CreateScope()
            Dim repo = scope.ServiceProvider.GetRequiredService(Of IUserRepository)()
            Return Await repo.GetByIdAsync(id)
        End Using
    End Function
End Class

バックグラウンド処理でスコープを作成しない

' BAD: スコープサービスにスコープなし
Public Class BadBackgroundService
    Inherits BackgroundService

    Private ReadOnly _orderService As IOrderService  ' スコープ — 例外が発生する!
End Class

' GOOD: 処理単位ごとにスコープを作成する
Public Class GoodBackgroundService
    Inherits BackgroundService

    Private ReadOnly _scopeFactory As IServiceScopeFactory

    Protected Overrides Async Function ExecuteAsync(ct As CancellationToken) As Task
        Using scope = _scopeFactory.CreateScope()
            Dim orderService = scope.ServiceProvider.GetRequiredService(Of IOrderService)()
            ' ...
        End Using
    End Function
End Class

リソース

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.