dotnet-backend-patterns
C#/.NETバックエンド開発のパターンを網羅し、堅牢なAPI・MCPサーバー・エンタープライズアプリケーションの構築を支援します。async/await、依存性注入、Entity Framework Core、Dapper、設定管理、キャッシュ、xUnitによるテストなどを幅広くカバーします。.NETバックエンドの開発・C#コードのレビュー・APIアーキテクチャの設計時に活用してください。
description の原文を見る
Master C#/.NET backend development patterns for building robust APIs, MCP servers, and enterprise applications. Covers async/await, dependency injection, Entity Framework Core, Dapper, configuration, caching, and testing with xUnit. Use when developing .NET backends, reviewing C# code, or designing API architectures.
SKILL.md 本文
.NET バックエンド開発パターン
C#/.NETの最新パターンをマスターして、本番レベルのAPI、MCPサーバー、エンタープライズバックエンドを最新のベストプラクティス(2024/2025)で構築します。
このスキルを使う場面
- 新しい.NET Web APIまたはMCPサーバーを開発している
- C#コードの品質とパフォーマンスをレビューしている
- 依存性注入を使用したサービスアーキテクチャを設計している
- Redisを使用したキャッシング戦略を実装している
- ユニットテストと統合テストを記述している
- EF CoreまたはDapperを使用したデータベースアクセスを最適化している
- IOptionsパターンを使用したアプリケーションを構成している
- エラーハンドリングとレジリエンスパターンを実装している
コアコンセプト
1. プロジェクト構造(クリーンアーキテクチャ)
src/
├── Domain/ # コアビジネスロジック(依存関係なし)
│ ├── Entities/
│ ├── Interfaces/
│ ├── Exceptions/
│ └── ValueObjects/
├── Application/ # ユースケース、DTO、バリデーション
│ ├── Services/
│ ├── DTOs/
│ ├── Validators/
│ └── Interfaces/
├── Infrastructure/ # 外部実装
│ ├── Data/ # EF Core、Dapperリポジトリ
│ ├── Caching/ # Redis、メモリキャッシュ
│ ├── External/ # HTTPクライアント、サードパーティAPI
│ └── DependencyInjection/ # サービス登録
└── Api/ # エントリーポイント
├── Controllers/ # またはMinimalAPIエンドポイント
├── Middleware/
├── Filters/
└── Program.cs
2. 依存性注入パターン
// ライフタイムごとのサービス登録
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddApplicationServices(
this IServiceCollection services,
IConfiguration configuration)
{
// Scoped: HTTPリクエストごとに1つのインスタンス
services.AddScoped<IProductService, ProductService>();
services.AddScoped<IOrderService, OrderService>();
// Singleton: アプリケーション全体で1つのインスタンス
services.AddSingleton<ICacheService, RedisCacheService>();
services.AddSingleton<IConnectionMultiplexer>(_ =>
ConnectionMultiplexer.Connect(configuration["Redis:Connection"]!));
// Transient: 毎回新しいインスタンスを生成
services.AddTransient<IValidator<CreateOrderRequest>, CreateOrderValidator>();
// オプションパターンで構成
services.Configure<CatalogOptions>(configuration.GetSection("Catalog"));
services.Configure<RedisOptions>(configuration.GetSection("Redis"));
// 条件付き生成用のファクトリパターン
services.AddScoped<IPriceCalculator>(sp =>
{
var options = sp.GetRequiredService<IOptions<PricingOptions>>().Value;
return options.UseNewEngine
? sp.GetRequiredService<NewPriceCalculator>()
: sp.GetRequiredService<LegacyPriceCalculator>();
});
// キー付きサービス(.NET 8以降)
services.AddKeyedScoped<IPaymentProcessor, StripeProcessor>("stripe");
services.AddKeyedScoped<IPaymentProcessor, PayPalProcessor>("paypal");
return services;
}
}
// キー付きサービスの使用方法
public class CheckoutService
{
public CheckoutService(
[FromKeyedServices("stripe")] IPaymentProcessor stripeProcessor)
{
_processor = stripeProcessor;
}
}
3. Async/Awaitパターン
// ✅ 正解: 下層まで非同期
public async Task<Product> GetProductAsync(string id, CancellationToken ct = default)
{
return await _repository.GetByIdAsync(id, ct);
}
// ✅ 正解: WhenAllで並列実行
public async Task<(Stock, Price)> GetStockAndPriceAsync(
string productId,
CancellationToken ct = default)
{
var stockTask = _stockService.GetAsync(productId, ct);
var priceTask = _priceService.GetAsync(productId, ct);
await Task.WhenAll(stockTask, priceTask);
return (await stockTask, await priceTask);
}
// ✅ 正解: ライブラリではConfigureAwaitを使用
public async Task<T> LibraryMethodAsync<T>(CancellationToken ct = default)
{
var result = await _httpClient.GetAsync(url, ct).ConfigureAwait(false);
return await result.Content.ReadFromJsonAsync<T>(ct).ConfigureAwait(false);
}
// ✅ 正解: キャッシング用ホットパスでValueTask
public ValueTask<Product?> GetCachedProductAsync(string id)
{
if (_cache.TryGetValue(id, out Product? product))
return ValueTask.FromResult(product);
return new ValueTask<Product?>(GetFromDatabaseAsync(id));
}
// ❌ 不正解: 非同期のブロック(デッドロックリスク)
var result = GetProductAsync(id).Result; // 絶対にするな
var result2 = GetProductAsync(id).GetAwaiter().GetResult(); // これも悪い
// ❌ 不正解: async void(イベントハンドラ以外)
public async void ProcessOrder() { } // 例外が失われる
// ❌ 不正解: すでに非同期のコードで不要なTask.Run
await Task.Run(async () => await GetDataAsync()); // スレッドを無駄にする
4. IOptionsでの構成
// 構成クラス
public class CatalogOptions
{
public const string SectionName = "Catalog";
public int DefaultPageSize { get; set; } = 50;
public int MaxPageSize { get; set; } = 200;
public TimeSpan CacheDuration { get; set; } = TimeSpan.FromMinutes(15);
public bool EnableEnrichment { get; set; } = true;
}
public class RedisOptions
{
public const string SectionName = "Redis";
public string Connection { get; set; } = "localhost:6379";
public string KeyPrefix { get; set; } = "mcp:";
public int Database { get; set; } = 0;
}
// appsettings.json
{
"Catalog": {
"DefaultPageSize": 50,
"MaxPageSize": 200,
"CacheDuration": "00:15:00",
"EnableEnrichment": true
},
"Redis": {
"Connection": "localhost:6379",
"KeyPrefix": "mcp:",
"Database": 0
}
}
// 登録
services.Configure<CatalogOptions>(configuration.GetSection(CatalogOptions.SectionName));
services.Configure<RedisOptions>(configuration.GetSection(RedisOptions.SectionName));
// IOptionsで使用(シングルトン、スタートアップ時に1回読み込み)
public class CatalogService
{
private readonly CatalogOptions _options;
public CatalogService(IOptions<CatalogOptions> options)
{
_options = options.Value;
}
}
// IOptionsSnapshotで使用(スコープ、リクエストごとに再読み込み)
public class DynamicService
{
private readonly CatalogOptions _options;
public DynamicService(IOptionsSnapshot<CatalogOptions> options)
{
_options = options.Value; // リクエストごとに新しい値
}
}
// IOptionsMonitorで使用(シングルトン、変更時に通知)
public class MonitoredService
{
private CatalogOptions _options;
public MonitoredService(IOptionsMonitor<CatalogOptions> monitor)
{
_options = monitor.CurrentValue;
monitor.OnChange(newOptions => _options = newOptions);
}
}
5. Resultパターン(フロー制御のための例外回避)
// 汎用Resultタイプ
public class Result<T>
{
public bool IsSuccess { get; }
public T? Value { get; }
public string? Error { get; }
public string? ErrorCode { get; }
private Result(bool isSuccess, T? value, string? error, string? errorCode)
{
IsSuccess = isSuccess;
Value = value;
Error = error;
ErrorCode = errorCode;
}
public static Result<T> Success(T value) => new(true, value, null, null);
public static Result<T> Failure(string error, string? code = null) => new(false, default, error, code);
public Result<TNew> Map<TNew>(Func<T, TNew> mapper) =>
IsSuccess ? Result<TNew>.Success(mapper(Value!)) : Result<TNew>.Failure(Error!, ErrorCode);
public async Task<Result<TNew>> MapAsync<TNew>(Func<T, Task<TNew>> mapper) =>
IsSuccess ? Result<TNew>.Success(await mapper(Value!)) : Result<TNew>.Failure(Error!, ErrorCode);
}
// サービスでの使用
public async Task<Result<Order>> CreateOrderAsync(CreateOrderRequest request, CancellationToken ct)
{
// バリデーション
var validation = await _validator.ValidateAsync(request, ct);
if (!validation.IsValid)
return Result<Order>.Failure(
validation.Errors.First().ErrorMessage,
"VALIDATION_ERROR");
// ビジネスルールチェック
var stock = await _stockService.CheckAsync(request.ProductId, request.Quantity, ct);
if (!stock.IsAvailable)
return Result<Order>.Failure(
$"Insufficient stock: {stock.Available} available, {request.Quantity} requested",
"INSUFFICIENT_STOCK");
// 注文を作成
var order = await _repository.CreateAsync(request.ToEntity(), ct);
return Result<Order>.Success(order);
}
// コントローラー/エンドポイントでの使用
app.MapPost("/orders", async (
CreateOrderRequest request,
IOrderService orderService,
CancellationToken ct) =>
{
var result = await orderService.CreateOrderAsync(request, ct);
return result.IsSuccess
? Results.Created($"/orders/{result.Value!.Id}", result.Value)
: Results.BadRequest(new { error = result.Error, code = result.ErrorCode });
});
データアクセスパターン
Entity Framework Core
// DbContextの構成
public class AppDbContext : DbContext
{
public DbSet<Product> Products => Set<Product>();
public DbSet<Order> Orders => Set<Order>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// アセンブリからすべての構成を適用
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
// グローバルクエリフィルター
modelBuilder.Entity<Product>().HasQueryFilter(p => !p.IsDeleted);
}
}
// エンティティ構成
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.ToTable("Products");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).HasMaxLength(40);
builder.Property(p => p.Name).HasMaxLength(200).IsRequired();
builder.Property(p => p.Price).HasPrecision(18, 2);
builder.HasIndex(p => p.Sku).IsUnique();
builder.HasIndex(p => new { p.CategoryId, p.Name });
builder.HasMany(p => p.OrderItems)
.WithOne(oi => oi.Product)
.HasForeignKey(oi => oi.ProductId);
}
}
// EF Coreを使用したリポジトリ
public class ProductRepository : IProductRepository
{
private readonly AppDbContext _context;
public async Task<Product?> GetByIdAsync(string id, CancellationToken ct = default)
{
return await _context.Products
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == id, ct);
}
public async Task<IReadOnlyList<Product>> SearchAsync(
ProductSearchCriteria criteria,
CancellationToken ct = default)
{
var query = _context.Products.AsNoTracking();
if (!string.IsNullOrWhiteSpace(criteria.SearchTerm))
query = query.Where(p => EF.Functions.Like(p.Name, $"%{criteria.SearchTerm}%"));
if (criteria.CategoryId.HasValue)
query = query.Where(p => p.CategoryId == criteria.CategoryId);
if (criteria.MinPrice.HasValue)
query = query.Where(p => p.Price >= criteria.MinPrice);
if (criteria.MaxPrice.HasValue)
query = query.Where(p => p.Price <= criteria.MaxPrice);
return await query
.OrderBy(p => p.Name)
.Skip((criteria.Page - 1) * criteria.PageSize)
.Take(criteria.PageSize)
.ToListAsync(ct);
}
}
パフォーマンスのためのDapper
public class DapperProductRepository : IProductRepository
{
private readonly IDbConnection _connection;
public async Task<Product?> GetByIdAsync(string id, CancellationToken ct = default)
{
const string sql = """
SELECT Id, Name, Sku, Price, CategoryId, Stock, CreatedAt
FROM Products
WHERE Id = @Id AND IsDeleted = 0
""";
return await _connection.QueryFirstOrDefaultAsync<Product>(
new CommandDefinition(sql, new { Id = id }, cancellationToken: ct));
}
public async Task<IReadOnlyList<Product>> SearchAsync(
ProductSearchCriteria criteria,
CancellationToken ct = default)
{
var sql = new StringBuilder("""
SELECT Id, Name, Sku, Price, CategoryId, Stock, CreatedAt
FROM Products
WHERE IsDeleted = 0
""");
var parameters = new DynamicParameters();
if (!string.IsNullOrWhiteSpace(criteria.SearchTerm))
{
sql.Append(" AND Name LIKE @SearchTerm");
parameters.Add("SearchTerm", $"%{criteria.SearchTerm}%");
}
if (criteria.CategoryId.HasValue)
{
sql.Append(" AND CategoryId = @CategoryId");
parameters.Add("CategoryId", criteria.CategoryId);
}
if (criteria.MinPrice.HasValue)
{
sql.Append(" AND Price >= @MinPrice");
parameters.Add("MinPrice", criteria.MinPrice);
}
if (criteria.MaxPrice.HasValue)
{
sql.Append(" AND Price <= @MaxPrice");
parameters.Add("MaxPrice", criteria.MaxPrice);
}
sql.Append(" ORDER BY Name OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY");
parameters.Add("Offset", (criteria.Page - 1) * criteria.PageSize);
parameters.Add("PageSize", criteria.PageSize);
var results = await _connection.QueryAsync<Product>(
new CommandDefinition(sql.ToString(), parameters, cancellationToken: ct));
return results.ToList();
}
// 関連データのマルチマッピング
public async Task<Order?> GetOrderWithItemsAsync(int orderId, CancellationToken ct = default)
{
const string sql = """
SELECT o.*, oi.*, p.*
FROM Orders o
LEFT JOIN OrderItems oi ON o.Id = oi.OrderId
LEFT JOIN Products p ON oi.ProductId = p.Id
WHERE o.Id = @OrderId
""";
var orderDictionary = new Dictionary<int, Order>();
await _connection.QueryAsync<Order, OrderItem, Product, Order>(
new CommandDefinition(sql, new { OrderId = orderId }, cancellationToken: ct),
(order, item, product) =>
{
if (!orderDictionary.TryGetValue(order.Id, out var existingOrder))
{
existingOrder = order;
existingOrder.Items = new List<OrderItem>();
orderDictionary.Add(order.Id, existingOrder);
}
if (item != null)
{
item.Product = product;
existingOrder.Items.Add(item);
}
return existingOrder;
},
splitOn: "Id,Id");
return orderDictionary.Values.FirstOrDefault();
}
}
キャッシングパターン
Redisを使用したマルチレベルキャッシュ
public class CachedProductService : IProductService
{
private readonly IProductRepository _repository;
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
private readonly ILogger<CachedProductService> _logger;
private static readonly TimeSpan MemoryCacheDuration = TimeSpan.FromMinutes(1);
private static readonly TimeSpan DistributedCacheDuration = TimeSpan.FromMinutes(15);
public async Task<Product?> GetByIdAsync(string id, CancellationToken ct = default)
{
var cacheKey = $"product:{id}";
// L1: メモリキャッシュ(プロセス内、最速)
if (_memoryCache.TryGetValue(cacheKey, out Product? cached))
{
_logger.LogDebug("L1 cache hit for {CacheKey}", cacheKey);
return cached;
}
// L2: 分散キャッシュ(Redis)
var distributed = await _distributedCache.GetStringAsync(cacheKey, ct);
if (distributed != null)
{
_logger.LogDebug("L2 cache hit for {CacheKey}", cacheKey);
var product = JsonSerializer.Deserialize<Product>(distributed);
// L1に格納
_memoryCache.Set(cacheKey, product, MemoryCacheDuration);
return product;
}
// L3: データベース
_logger.LogDebug("Cache miss for {CacheKey}, fetching from database", cacheKey);
var fromDb = await _repository.GetByIdAsync(id, ct);
if (fromDb != null)
{
var serialized = JsonSerializer.Serialize(fromDb);
// 両方のキャッシュに格納
await _distributedCache.SetStringAsync(
cacheKey,
serialized,
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = DistributedCacheDuration
},
ct);
_memoryCache.Set(cacheKey, fromDb, MemoryCacheDuration);
}
return fromDb;
}
public async Task InvalidateAsync(string id, CancellationToken ct = default)
{
var cacheKey = $"product:{id}";
_memoryCache.Remove(cacheKey);
await _distributedCache.RemoveAsync(cacheKey, ct);
_logger.LogInformation("Invalidated cache for {CacheKey}", cacheKey);
}
}
// Stale-While-Revalidateパターン
public class StaleWhileRevalidateCache<T>
{
private readonly IDistributedCache _cache;
private readonly TimeSpan _freshDuration;
private readonly TimeSpan _staleDuration;
public async Task<T?> GetOrCreateAsync(
string key,
Func<CancellationToken, Task<T>> factory,
CancellationToken ct = default)
{
var cached = await _cache.GetStringAsync(key, ct);
if (cached != null)
{
var entry = JsonSerializer.Deserialize<CacheEntry<T>>(cached)!;
if (entry.IsStale && !entry.IsExpired)
{
// 古いデータをすぐに返す、バックグラウンドで更新
_ = Task.Run(async () =>
{
var fresh = await factory(CancellationToken.None);
await SetAsync(key, fresh, CancellationToken.None);
});
}
if (!entry.IsExpired)
return entry.Value;
}
// キャッシュミスまたは期限切れ
var value = await factory(ct);
await SetAsync(key, value, ct);
return value;
}
private record CacheEntry<TValue>(TValue Value, DateTime CreatedAt)
{
public bool IsStale => DateTime.UtcNow - CreatedAt > _freshDuration;
public bool IsExpired => DateTime.UtcNow - CreatedAt > _staleDuration;
}
}
テストパターン
xUnitとMoqを使用したユニットテスト
public class OrderServiceTests
{
private readonly Mock<IOrderRepository> _mockRepository;
private readonly Mock<IStockService> _mockStockService;
private readonly Mock<IValidator<CreateOrderRequest>> _mockValidator;
private readonly OrderService _sut; // System Under Test
public OrderServiceTests()
{
_mockRepository = new Mock<IOrderRepository>();
_mockStockService = new Mock<IStockService>();
_mockValidator = new Mock<IValidator<CreateOrderRequest>>();
// デフォルト: バリデーションが通る
_mockValidator
.Setup(v => v.ValidateAsync(It.IsAny<CreateOrderRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new ValidationResult());
_sut = new OrderService(
_mockRepository.Object,
_mockStockService.Object,
_mockValidator.Object);
}
[Fact]
public async Task CreateOrderAsync_WithValidRequest_ReturnsSuccess()
{
// Arrange
var request = new CreateOrderRequest
{
ProductId = "PROD-001",
Quantity = 5,
CustomerOrderCode = "ORD-2024-001"
};
_mockStockService
.Setup(s => s.CheckAsync("PROD-001", 5, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StockResult { IsAvailable = true, Available = 10 });
_mockRepository
.Setup(r => r.CreateAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new Order { Id = 1, CustomerOrderCode = "ORD-2024-001" });
// Act
var result = await _sut.CreateOrderAsync(request);
// Assert
Assert.True(result.IsSuccess);
Assert.NotNull(result.Value);
Assert.Equal(1, result.Value.Id);
_mockRepository.Verify(
r => r.CreateAsync(It.Is<Order>(o => o.CustomerOrderCode == "ORD-2024-001"),
It.IsAny<CancellationToken>()),
Times.Once);
}
[Fact]
public async Task CreateOrderAsync_WithInsufficientStock_ReturnsFailure()
{
// Arrange
var request = new CreateOrderRequest { ProductId = "PROD-001", Quantity = 100 };
_mockStockService
.Setup(s => s.CheckAsync(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new StockResult { IsAvailable = false, Available = 5 });
// Act
var result = await _sut.CreateOrderAsync(request);
// Assert
Assert.False(result.IsSuccess);
Assert.Equal("INSUFFICIENT_STOCK", result.ErrorCode);
Assert.Contains("5 available", result.Error);
_mockRepository.Verify(
r => r.CreateAsync(It.IsAny<Order>(), It.IsAny<CancellationToken>()),
Times.Never);
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(-100)]
public async Task CreateOrderAsync_WithInvalidQuantity_ReturnsValidationError(int quantity)
{
// Arrange
var request = new CreateOrderRequest { ProductId = "PROD-001", Quantity = quantity };
_mockValidator
.Setup(v => v.ValidateAsync(request, It.IsAny<CancellationToken>()))
.ReturnsAsync(new ValidationResult(new[]
{
new ValidationFailure("Quantity", "Quantity must be greater than 0")
}));
// Act
var result = await _sut.CreateOrderAsync(request);
// Assert
Assert.False(result.IsSuccess);
Assert.Equal("VALIDATION_ERROR", result.ErrorCode);
}
}
WebApplicationFactoryを使用した統合テスト
public class ProductsApiTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
private readonly HttpClient _client;
public ProductsApiTests(WebApplicationFactory<Program> factory)
{
_factory = factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// 実データベースをインメモリに置き換える
services.RemoveAll<DbContextOptions<AppDbContext>>();
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("TestDb"));
// Redisをメモリキャッシュに置き換える
services.RemoveAll<IDistributedCache>();
services.AddDistributedMemoryCache();
});
});
_client = _factory.CreateClient();
}
[Fact]
public async Task GetProduct_WithValidId_ReturnsProduct()
{
// Arrange
using var scope = _factory.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
context.Products.Add(new Product
{
Id = "TEST-001",
Name = "Test Product",
Price = 99.99m
});
await context.SaveChangesAsync();
// Act
var response = await _client.GetAsync("/api/products/TEST-001");
// Assert
response.EnsureSuccessStatusCode();
var product = await response.Content.ReadFromJsonAsync<Product>();
Assert.Equal("Test Product", product!.Name);
}
[Fact]
public async Task GetProduct_WithInvalidId_Returns404()
{
// Act
var response = await _client.GetAsync("/api/products/NONEXISTENT");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
}
ベストプラクティス
すること
- 非同期/待機を呼び出しスタック全体で使用する
- コンストラクタインジェクションを通じて依存関係を注入する
- **IOptions<T>**を型付け構成に使用する
- ビジネスロジックのために例外をスローする代わりにResultタイプを返す
- CancellationTokenをすべての非同期メソッドで使用する
- 読み取り集約的でパフォーマンス重視のクエリには Dapper を優先する
- 複雑なドメインモデルには EF Core を使用する(変更追跡付き)
- 適切な無効化戦略で積極的にキャッシュする
- ビジネスロジック用のユニットテスト、API用の統合テストを記述する
- DTO と不変データに record タイプを使用する
しないこと
.Resultまたは.Wait()で非同期をブロックしない- イベントハンドラ以外で
async voidを使用しない Generic Exceptionをキャッチしない(再スロー またはログなし)- 構成値をハードコードしない
- API で EF エンティティを直接公開しない(DTO を使用)
- 読み取り専用クエリで
AsNoTracking()を忘れない - CancellationToken パラメータを無視しない
- 手動で
new HttpClient()を作成しない(IHttpClientFactory を使用) - 非同期コードと同期コードを不必要に混在させない
- API 境界でバリデーションをスキップしない
よくあるピットフォール
- N+1クエリ:
.Include()または明示的な結合を使用 - メモリリーク: IDisposable リソースを破棄、
usingを使用 - デッドロック: 非同期と同期を混在させない、ライブラリで
ConfigureAwait(false)を使用 - オーバーフェッチ: 必要な列のみを選択、プロジェクションを使用
- インデックスなし: クエリプランを確認、一般的なフィルターにインデックスを追加
- タイムアウト問題: HTTPクライアントに適切なタイムアウトを構成
- キャッシュスタンピード: キャッシュポピュレーション用の分散ロックを使用
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- wshobson
- リポジトリ
- wshobson/agents
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/wshobson/agents / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。