.NET 8 によるクラウドネイティブ開発:Azure と AWS での実践ガイド

はじめに

.NET 8 は、クラウドネイティブアプリケーション開発において画期的な進化を遂げました。本記事では、.NET 8 の新機能を活用しながら、Azure と AWS の両プラットフォームでエンタープライズグレードのアプリケーションを構築する方法を詳しく解説します。

.NET 8 の主要な新機能

Native AOT(Ahead-of-Time Compilation)

Native AOT により、.NET アプリケーションをネイティブコードにコンパイルし、起動時間とメモリ使用量を大幅に削減できます。

// Program.cs - Native AOT 対応の最小 API
var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapGet("/", () => new { Message = "Hello from Native AOT!" });
app.MapGet("/health", () => Results.Ok(new { Status = "Healthy" }));

app.Run();

// JSON シリアライゼーション用のコンテキスト
[JsonSerializable(typeof(WeatherForecast))]
[JsonSerializable(typeof(HealthStatus))]
internal partial class AppJsonSerializerContext : JsonSerializerContext
{
}

プロジェクト設定

<!-- .csproj ファイル -->
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <PublishAot>true</PublishAot>
    <StripSymbols>true</StripSymbols>
    <InvariantGlobalization>true</InvariantGlobalization>
  </PropertyGroup>
</Project>

クラウドネイティブアーキテクチャ

Clean Architecture の実装

// Domain/Entities/Order.cs
namespace ECommerce.Domain.Entities;

public class Order : AggregateRoot
{
    public Guid Id { get; private set; }
    public string CustomerId { get; private set; }
    public DateTime OrderDate { get; private set; }
    public OrderStatus Status { get; private set; }
    public Money TotalAmount { get; private set; }
    
    private readonly List<OrderItem> _items = new();
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
    
    protected Order() { } // EF Core
    
    public static Order Create(string customerId)
    {
        var order = new Order
        {
            Id = Guid.NewGuid(),
            CustomerId = customerId ?? throw new ArgumentNullException(nameof(customerId)),
            OrderDate = DateTime.UtcNow,
            Status = OrderStatus.Pending,
            TotalAmount = Money.Zero
        };
        
        order.AddDomainEvent(new OrderCreatedEvent(order.Id, customerId));
        return order;
    }
    
    public void AddItem(Product product, int quantity)
    {
        if (quantity <= 0)
            throw new DomainException("Quantity must be greater than zero");
            
        var existingItem = _items.FirstOrDefault(i => i.ProductId == product.Id);
        
        if (existingItem != null)
        {
            existingItem.IncreaseQuantity(quantity);
        }
        else
        {
            _items.Add(new OrderItem(product.Id, product.Name, product.Price, quantity));
        }
        
        RecalculateTotal();
    }
    
    private void RecalculateTotal()
    {
        TotalAmount = new Money(_items.Sum(i => i.TotalPrice.Amount));
    }
}

// Domain/ValueObjects/Money.cs
public record Money
{
    public decimal Amount { get; }
    public string Currency { get; }
    
    public Money(decimal amount, string currency = "JPY")
    {
        if (amount < 0)
            throw new ArgumentException("Amount cannot be negative");
            
        Amount = amount;
        Currency = currency;
    }
    
    public static Money Zero => new(0);
    
    public static Money operator +(Money left, Money right)
    {
        if (left.Currency != right.Currency)
            throw new InvalidOperationException("Cannot add different currencies");
            
        return new Money(left.Amount + right.Amount, left.Currency);
    }
}

リポジトリパターンと Unit of Work

// Application/Interfaces/IOrderRepository.cs
public interface IOrderRepository : IRepository<Order>
{
    Task<Order?> GetByIdWithItemsAsync(Guid orderId, CancellationToken cancellationToken = default);
    Task<IEnumerable<Order>> GetByCustomerIdAsync(string customerId, CancellationToken cancellationToken = default);
    Task<PagedResult<Order>> GetPagedAsync(int page, int pageSize, CancellationToken cancellationToken = default);
}

// Infrastructure/Persistence/Repositories/OrderRepository.cs
public class OrderRepository : Repository<Order>, IOrderRepository
{
    private readonly ApplicationDbContext _context;
    
    public OrderRepository(ApplicationDbContext context) : base(context)
    {
        _context = context;
    }
    
    public async Task<Order?> GetByIdWithItemsAsync(Guid orderId, CancellationToken cancellationToken = default)
    {
        return await _context.Orders
            .Include(o => o.Items)
            .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken);
    }
    
    public async Task<IEnumerable<Order>> GetByCustomerIdAsync(string customerId, CancellationToken cancellationToken = default)
    {
        return await _context.Orders
            .Where(o => o.CustomerId == customerId)
            .OrderByDescending(o => o.OrderDate)
            .ToListAsync(cancellationToken);
    }
    
    public async Task<PagedResult<Order>> GetPagedAsync(int page, int pageSize, CancellationToken cancellationToken = default)
    {
        var query = _context.Orders.AsQueryable();
        
        var totalCount = await query.CountAsync(cancellationToken);
        var items = await query
            .OrderByDescending(o => o.OrderDate)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);
            
        return new PagedResult<Order>(items, totalCount, page, pageSize);
    }
}

// Infrastructure/Persistence/UnitOfWork.cs
public class UnitOfWork : IUnitOfWork
{
    private readonly ApplicationDbContext _context;
    private readonly IMediator _mediator;
    
    public IOrderRepository Orders { get; }
    public IProductRepository Products { get; }
    
    public UnitOfWork(ApplicationDbContext context, IMediator mediator)
    {
        _context = context;
        _mediator = mediator;
        
        Orders = new OrderRepository(_context);
        Products = new ProductRepository(_context);
    }
    
    public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        // ドメインイベントの発行
        var domainEvents = _context.ChangeTracker.Entries<AggregateRoot>()
            .SelectMany(e => e.Entity.DomainEvents)
            .ToList();
            
        var result = await _context.SaveChangesAsync(cancellationToken);
        
        // イベントの発行
        foreach (var domainEvent in domainEvents)
        {
            await _mediator.Publish(domainEvent, cancellationToken);
        }
        
        return result;
    }
}

Azure での実装

Azure Functions with .NET 8

// Functions/OrderProcessingFunction.cs
public class OrderProcessingFunction
{
    private readonly IOrderService _orderService;
    private readonly ILogger<OrderProcessingFunction> _logger;
    
    public OrderProcessingFunction(IOrderService orderService, ILogger<OrderProcessingFunction> logger)
    {
        _orderService = orderService;
        _logger = logger;
    }
    
    [Function("ProcessOrder")]
    public async Task<IActionResult> ProcessOrder(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders/process")] HttpRequest req,
        CancellationToken cancellationToken)
    {
        try
        {
            var orderRequest = await req.GetBodyAsync<ProcessOrderRequest>();
            
            if (!orderRequest.IsValid())
            {
                return new BadRequestObjectResult("Invalid order request");
            }
            
            var result = await _orderService.ProcessOrderAsync(orderRequest, cancellationToken);
            
            return new OkObjectResult(new
            {
                OrderId = result.OrderId,
                Status = result.Status,
                Message = "Order processed successfully"
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing order");
            return new StatusCodeResult(StatusCodes.Status500InternalServerError);
        }
    }
    
    [Function("OrderStatusWebhook")]
    public async Task OrderStatusWebhook(
        [ServiceBusTrigger("order-events", "order-status-subscription")] OrderStatusChangedEvent orderEvent,
        [CosmosDB(
            databaseName: "OrdersDB",
            containerName: "OrderStatusHistory",
            Connection = "CosmosDBConnection")] IAsyncCollector<OrderStatusHistory> statusHistory,
        CancellationToken cancellationToken)
    {
        _logger.LogInformation($"Processing order status change: {orderEvent.OrderId} -> {orderEvent.NewStatus}");
        
        await statusHistory.AddAsync(new OrderStatusHistory
        {
            Id = Guid.NewGuid().ToString(),
            OrderId = orderEvent.OrderId,
            OldStatus = orderEvent.OldStatus,
            NewStatus = orderEvent.NewStatus,
            ChangedAt = orderEvent.Timestamp,
            ChangedBy = orderEvent.UserId
        });
    }
}

// Startup configuration
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices((context, services) =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        
        // Azure サービスの設定
        services.AddAzureClients(builder =>
        {
            builder.AddServiceBusClient(context.Configuration.GetConnectionString("ServiceBus"));
            builder.AddBlobServiceClient(context.Configuration.GetConnectionString("Storage"));
            builder.UseCredential(new DefaultAzureCredential());
        });
        
        // DI 設定
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(context.Configuration.GetConnectionString("SqlDatabase")));
            
        services.AddScoped<IUnitOfWork, UnitOfWork>();
        services.AddScoped<IOrderService, OrderService>();
        
        // MediatR
        services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
    })
    .Build();

host.Run();

Azure Service Bus Integration

// Infrastructure/Messaging/ServiceBusMessagePublisher.cs
public class ServiceBusMessagePublisher : IMessagePublisher
{
    private readonly ServiceBusClient _client;
    private readonly ILogger<ServiceBusMessagePublisher> _logger;
    
    public ServiceBusMessagePublisher(ServiceBusClient client, ILogger<ServiceBusMessagePublisher> logger)
    {
        _client = client;
        _logger = logger;
    }
    
    public async Task PublishAsync<T>(T message, string topicName, CancellationToken cancellationToken = default)
        where T : IMessage
    {
        var sender = _client.CreateSender(topicName);
        
        try
        {
            var serviceBusMessage = new ServiceBusMessage
            {
                Body = BinaryData.FromObjectAsJson(message),
                ContentType = "application/json",
                Subject = message.GetType().Name,
                MessageId = Guid.NewGuid().ToString(),
                CorrelationId = message.CorrelationId
            };
            
            // カスタムプロパティの追加
            serviceBusMessage.ApplicationProperties.Add("MessageType", message.GetType().FullName);
            serviceBusMessage.ApplicationProperties.Add("Timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds());
            
            await sender.SendMessageAsync(serviceBusMessage, cancellationToken);
            
            _logger.LogInformation("Message published to {TopicName}: {MessageType}", topicName, message.GetType().Name);
        }
        finally
        {
            await sender.DisposeAsync();
        }
    }
    
    public async Task PublishBatchAsync<T>(IEnumerable<T> messages, string topicName, CancellationToken cancellationToken = default)
        where T : IMessage
    {
        var sender = _client.CreateSender(topicName);
        
        try
        {
            var batch = await sender.CreateMessageBatchAsync(cancellationToken);
            
            foreach (var message in messages)
            {
                var serviceBusMessage = new ServiceBusMessage
                {
                    Body = BinaryData.FromObjectAsJson(message),
                    ContentType = "application/json",
                    Subject = message.GetType().Name
                };
                
                if (!batch.TryAddMessage(serviceBusMessage))
                {
                    // バッチが満杯の場合は送信して新しいバッチを作成
                    await sender.SendMessagesAsync(batch, cancellationToken);
                    batch = await sender.CreateMessageBatchAsync(cancellationToken);
                    batch.TryAddMessage(serviceBusMessage);
                }
            }
            
            if (batch.Count > 0)
            {
                await sender.SendMessagesAsync(batch, cancellationToken);
            }
        }
        finally
        {
            await sender.DisposeAsync();
        }
    }
}

AWS での実装

AWS Lambda with .NET 8

// Lambda/Functions/OrderProcessingLambda.cs
public class OrderProcessingLambda
{
    private readonly IOrderService _orderService;
    private readonly IAmazonSQS _sqsClient;
    private readonly IAmazonDynamoDB _dynamoDbClient;
    
    public OrderProcessingLambda()
    {
        // Lambda では DI コンテナの設定
        var services = new ServiceCollection();
        ConfigureServices(services);
        var serviceProvider = services.BuildServiceProvider();
        
        _orderService = serviceProvider.GetRequiredService<IOrderService>();
        _sqsClient = serviceProvider.GetRequiredService<IAmazonSQS>();
        _dynamoDbClient = serviceProvider.GetRequiredService<IAmazonDynamoDB>();
    }
    
    [LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
    public async Task<APIGatewayProxyResponse> ProcessOrderHandler(
        APIGatewayProxyRequest request,
        ILambdaContext context)
    {
        context.Logger.LogInformation($"Processing order request: {request.RequestContext.RequestId}");
        
        try
        {
            var orderRequest = JsonSerializer.Deserialize<ProcessOrderRequest>(request.Body);
            
            var result = await _orderService.ProcessOrderAsync(orderRequest);
            
            // DynamoDB に注文履歴を保存
            await SaveOrderHistory(result);
            
            // SQS にメッセージを送信
            await PublishOrderEvent(result);
            
            return new APIGatewayProxyResponse
            {
                StatusCode = 200,
                Headers = new Dictionary<string, string>
                {
                    { "Content-Type", "application/json" },
                    { "X-Request-Id", context.RequestId }
                },
                Body = JsonSerializer.Serialize(new
                {
                    success = true,
                    orderId = result.OrderId,
                    status = result.Status
                })
            };
        }
        catch (ValidationException ex)
        {
            return new APIGatewayProxyResponse
            {
                StatusCode = 400,
                Body = JsonSerializer.Serialize(new { error = ex.Message })
            };
        }
        catch (Exception ex)
        {
            context.Logger.LogError($"Error processing order: {ex}");
            
            return new APIGatewayProxyResponse
            {
                StatusCode = 500,
                Body = JsonSerializer.Serialize(new { error = "Internal server error" })
            };
        }
    }
    
    private async Task SaveOrderHistory(OrderResult result)
    {
        var putRequest = new PutItemRequest
        {
            TableName = Environment.GetEnvironmentVariable("ORDER_HISTORY_TABLE"),
            Item = new Dictionary<string, AttributeValue>
            {
                ["OrderId"] = new AttributeValue { S = result.OrderId },
                ["CustomerId"] = new AttributeValue { S = result.CustomerId },
                ["Timestamp"] = new AttributeValue { N = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString() },
                ["Status"] = new AttributeValue { S = result.Status },
                ["TotalAmount"] = new AttributeValue { N = result.TotalAmount.ToString() }
            }
        };
        
        await _dynamoDbClient.PutItemAsync(putRequest);
    }
    
    private async Task PublishOrderEvent(OrderResult result)
    {
        var message = new SendMessageRequest
        {
            QueueUrl = Environment.GetEnvironmentVariable("ORDER_QUEUE_URL"),
            MessageBody = JsonSerializer.Serialize(new OrderProcessedEvent
            {
                OrderId = result.OrderId,
                CustomerId = result.CustomerId,
                ProcessedAt = DateTime.UtcNow,
                Status = result.Status
            }),
            MessageAttributes = new Dictionary<string, MessageAttributeValue>
            {
                ["EventType"] = new MessageAttributeValue
                {
                    DataType = "String",
                    StringValue = "OrderProcessed"
                }
            }
        };
        
        await _sqsClient.SendMessageAsync(message);
    }
    
    private void ConfigureServices(IServiceCollection services)
    {
        // AWS サービスの設定
        services.AddDefaultAWSOptions(new AWSOptions
        {
            Region = RegionEndpoint.GetBySystemName(Environment.GetEnvironmentVariable("AWS_REGION"))
        });
        
        services.AddAWSService<IAmazonSQS>();
        services.AddAWSService<IAmazonDynamoDB>();
        services.AddAWSService<IAmazonS3>();
        
        // アプリケーションサービス
        services.AddScoped<IOrderService, OrderService>();
        services.AddScoped<IOrderRepository, DynamoDbOrderRepository>();
        
        // ロギング
        services.AddLogging(builder =>
        {
            builder.AddLambdaLogger();
            builder.SetMinimumLevel(LogLevel.Information);
        });
    }
}

Step Functions との統合

// StepFunctions/OrderWorkflow.cs
public class OrderWorkflowSteps
{
    [LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
    public async Task<ValidateOrderResult> ValidateOrder(OrderRequest request, ILambdaContext context)
    {
        context.Logger.LogInformation($"Validating order for customer: {request.CustomerId}");
        
        var validationResult = new ValidateOrderResult
        {
            OrderId = Guid.NewGuid().ToString(),
            IsValid = true,
            ValidationErrors = new List<string>()
        };
        
        // 在庫確認
        foreach (var item in request.Items)
        {
            var inventoryAvailable = await CheckInventory(item.ProductId, item.Quantity);
            if (!inventoryAvailable)
            {
                validationResult.IsValid = false;
                validationResult.ValidationErrors.Add($"Insufficient inventory for product {item.ProductId}");
            }
        }
        
        // 与信確認
        var creditCheck = await CheckCustomerCredit(request.CustomerId, request.TotalAmount);
        if (!creditCheck.Approved)
        {
            validationResult.IsValid = false;
            validationResult.ValidationErrors.Add("Credit check failed");
        }
        
        return validationResult;
    }
    
    [LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
    public async Task<ProcessPaymentResult> ProcessPayment(PaymentRequest request, ILambdaContext context)
    {
        context.Logger.LogInformation($"Processing payment for order: {request.OrderId}");
        
        // Stripe API の呼び出し
        var stripe = new StripeClient(Environment.GetEnvironmentVariable("STRIPE_SECRET_KEY"));
        
        try
        {
            var options = new ChargeCreateOptions
            {
                Amount = (long)(request.Amount * 100),
                Currency = "jpy",
                Source = request.PaymentToken,
                Description = $"Order {request.OrderId}",
                Metadata = new Dictionary<string, string>
                {
                    ["order_id"] = request.OrderId,
                    ["customer_id"] = request.CustomerId
                }
            };
            
            var charge = await stripe.Charges.CreateAsync(options);
            
            return new ProcessPaymentResult
            {
                Success = true,
                TransactionId = charge.Id,
                ProcessedAt = DateTime.UtcNow
            };
        }
        catch (StripeException ex)
        {
            context.Logger.LogError($"Payment failed: {ex.Message}");
            
            return new ProcessPaymentResult
            {
                Success = false,
                ErrorMessage = ex.Message
            };
        }
    }
}

パフォーマンス最適化

Response Compression

// Program.cs
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] 
    { 
        "application/json",
        "application/xml",
        "text/json"
    });
});

builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
    options.Level = CompressionLevel.Optimal;
});

builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
    options.Level = CompressionLevel.Optimal;
});

メモリ管理とオブジェクトプーリング

// Infrastructure/ObjectPooling/HttpClientPool.cs
public class HttpClientPool
{
    private readonly ObjectPool<HttpClient> _pool;
    
    public HttpClientPool()
    {
        var provider = new DefaultObjectPoolProvider();
        var policy = new HttpClientPooledObjectPolicy();
        _pool = provider.Create(policy);
    }
    
    public async Task<T> ExecuteAsync<T>(Func<HttpClient, Task<T>> operation)
    {
        var client = _pool.Get();
        try
        {
            return await operation(client);
        }
        finally
        {
            _pool.Return(client);
        }
    }
}

public class HttpClientPooledObjectPolicy : PooledObjectPolicy<HttpClient>
{
    public override HttpClient Create()
    {
        var handler = new SocketsHttpHandler
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(5),
            PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
            MaxConnectionsPerServer = 10
        };
        
        return new HttpClient(handler)
        {
            Timeout = TimeSpan.FromSeconds(30)
        };
    }
    
    public override bool Return(HttpClient obj)
    {
        // クリーンアップ
        obj.DefaultRequestHeaders.Clear();
        return true;
    }
}

監視とロギング

統合ロギング

// Infrastructure/Logging/StructuredLogging.cs
public static class LoggerExtensions
{
    public static void LogOrderProcessed(this ILogger logger, string orderId, string customerId, decimal amount)
    {
        logger.LogInformation("Order processed successfully. OrderId: {OrderId}, CustomerId: {CustomerId}, Amount: {Amount}",
            orderId, customerId, amount);
    }
    
    public static void LogPerformanceMetric(this ILogger logger, string operation, double duration)
    {
        using (logger.BeginScope(new Dictionary<string, object>
        {
            ["Operation"] = operation,
            ["Duration"] = duration,
            ["Timestamp"] = DateTime.UtcNow
        }))
        {
            logger.LogInformation("Performance metric recorded");
        }
    }
}

// Application Insights / CloudWatch 統合
builder.Services.AddApplicationInsightsTelemetry();
builder.Services.AddSingleton<ITelemetryInitializer, CustomTelemetryInitializer>();

public class CustomTelemetryInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        telemetry.Context.GlobalProperties["Environment"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
        telemetry.Context.GlobalProperties["ServiceName"] = "OrderService";
        telemetry.Context.GlobalProperties["Version"] = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
    }
}

セキュリティ実装

JWT 認証と認可

// Security/JwtConfiguration.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = builder.Configuration["Auth:Authority"];
        options.Audience = builder.Configuration["Auth:Audience"];
        options.RequireHttpsMetadata = true;
        
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.Zero
        };
        
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
                logger.LogError("Authentication failed: {Error}", context.Exception.Message);
                return Task.CompletedTask;
            },
            OnTokenValidated = context =>
            {
                var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
                logger.LogInformation("Token validated for user: {User}", context.Principal?.Identity?.Name);
                return Task.CompletedTask;
            }
        };
    });

// Policy-based authorization
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("OrderManagement", policy =>
        policy.RequireAuthenticatedUser()
              .RequireClaim("permission", "orders:manage"));
              
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"));
});

まとめ

.NET 8 は、クラウドネイティブアプリケーション開発において優れた選択肢です。Native AOT、改善されたパフォーマンス、そして Azure と AWS の両プラットフォームでの優れたサポートにより、エンタープライズグレードのアプリケーションを効率的に構築できます。

重要なポイント:

  • Native AOT による起動時間とメモリ使用量の削減
  • Clean Architecture による保守性の高い設計
  • クラウドサービスとの緊密な統合
  • 包括的な監視とロギング

エンハンスド株式会社では、.NET 8 を活用したクラウドネイティブアプリケーション開発を支援しています。お気軽にお問い合わせください。


タグ: #dotNET8 #CloudNative #Azure #AWS #CSharp #Serverless

執筆者: エンハンスド株式会社 .NET開発部

公開日: 2024年12月20日