Community코딩 & 개발github.com

akka-net-best-practices

Akka.NET 모범 사례: EventStream과 DistributedPubSub 비교, 감독 전략, 오류 처리, Props와 DependencyResolver 선택, 작업 분산 패턴, 클러스터/로컬 모드 추상화 등 중요한 패턴을 다룹니다.

akka-net-best-practices란 무엇인가요?

이 스킬은 Akka.NET 애플리케이션 개발 시 자주 마주치는 핵심 설계 결정과 모범 사례를 상세히 설명합니다. EventStream과 DistributedPubSub의 차이점을 명확히 구분하여 단일 프로세스와 클러스터 환경에서의 적절한 메시징 패턴을 제시합니다. 또한 액터 감독 전략의 실제 의미(자식 보호), 예외 처리 시 try-catch와 감독의 올바른 사용 기준, Props와 DependencyResolver 선택 기준, 작업 분산을 위한 데이터베이스 큐 및 Akka.Streams 패턴, 테스트 가능성을 위한 클러스터/로컬 모드 추상화까지 다룹니다. 이 스킬은 Claude Code, Cursor, Codex 등 모든 AI 어시스턴트가 참조할 수 있도록 설계되었으며, `work-distribution-patterns.md`, `cluster-local-abstractions.md`, `async-cancellation-patterns.md` 같은 추가 참조 파일과 연계됩니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/akka-best-practices

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Akka.NET 모범 사례

이 스킬을 사용해야 할 때

다음과 같은 경우에 이 스킬을 사용하십시오:

  • 액터 통신 패턴 설계
  • EventStream과 DistributedPubSub 사이에서 결정
  • 액터 내 오류 처리 구현
  • 감독 전략 이해
  • Props 패턴과 DependencyResolver 선택
  • 노드 간 작업 분산 설계
  • 클러스터 인프라 유무에 관계없이 실행 가능한 테스트 가능한 액터 시스템 생성
  • 로컬 테스트 시나리오를 위한 클러스터 샤딩 추상화

참조 파일


1. EventStream vs DistributedPubSub

중요: EventStream은 로컬 전용입니다

Context.System.EventStream은 단일 ActorSystem 프로세스에만 로컬입니다. 클러스터 노드 간에 동작하지 않습니다.

// 잘못된 예: 이것은 단일 서버에서만 동작합니다.
// 두 번째 서버를 추가하면 서버 2의 구독자는 서버 1의 이벤트를 수신하지 못합니다.
Context.System.EventStream.Subscribe(Self, typeof(PostCreated));
Context.System.EventStream.Publish(new PostCreated(postId, authorId));

EventStream이 적절한 경우:

  • 단일 프로세스 내 로깅 및 진단
  • 진정한 단일 프로세스 애플리케이션을 위한 로컬 이벤트 버스
  • 개발/테스트 시나리오

다중 노드에는 DistributedPubSub 사용

여러 클러스터 노드에 걸쳐 있어야 하는 이벤트의 경우 Akka.Cluster.Tools.PublishSubscribe를 사용하십시오:

using Akka.Cluster.Tools.PublishSubscribe;

public class TimelineUpdatePublisher : ReceiveActor
{
    private readonly IActorRef _mediator;

    public TimelineUpdatePublisher()
    {
        // DistributedPubSub 중재자 가져오기
        _mediator = DistributedPubSub.Get(Context.System).Mediator;

        Receive<PublishTimelineUpdate>(msg =>
        {
            // 토픽에 게시 - 모든 노드의 모든 구독자에게 도달
            _mediator.Tell(new Publish($"timeline:{msg.UserId}", msg.Update));
        });
    }
}

DistributedPubSub용 Akka.Hosting 구성

builder.WithDistributedPubSub(role: null); // 모든 역할에서 사용 가능, 또는 역할 지정

토픽 디자인 패턴

패턴토픽 형식사용 사례
사용자별timeline:{userId}타임라인 업데이트, 알림
엔티티별post:{postId}게시물 참여 업데이트
브로드캐스트system:announcements시스템 전체 알림
역할 기반workers:rss-poller작업 분산

2. 감독 전략

주요 설명: 감독은 자식을 위한 것입니다

액터에 정의된 감독 전략은 해당 액터가 자식을 감독하는 방법을 결정하며, 액터 자체가 감독되는 방법이 아닙니다.

public class ParentActor : ReceiveActor
{
    // 이 전략은 ParentActor 자체가 아닌 ParentActor의 자식에게 적용됩니다.
    protected override SupervisorStrategy SupervisorStrategy()
    {
        return new OneForOneStrategy(
            maxNrOfRetries: 10,
            withinTimeRange: TimeSpan.FromSeconds(30),
            decider: ex => ex switch
            {
                ArithmeticException => Directive.Resume,
                NullReferenceException => Directive.Restart,
                ArgumentException => Directive.Stop,
                _ => Directive.Escalate
            });
    }
}

기본 감독 전략

기본 OneForOneStrategy에는 이미 속도 제한이 포함되어 있습니다:

  • 1초 이내 10회 재시작 = 액터가 영구 중지됨
  • 이는 무한 재시작 루프를 방지합니다

특별한 요구 사항이 없다면 사용자 정의 전략이 거의 필요하지 않습니다.

사용자 정의 감독을 정의해야 하는 경우

좋은 이유:

  • 액터가 복구 불가능한 상태 손상을 나타내는 예외를 발생시킴 -> 재시작
  • 재시작을 유발해서는 안 되는 예외(예상된 실패)를 발생시킴 -> 재개
  • 자식 실패가 형제에게 영향을 미쳐야 함 -> AllForOneStrategy 사용
  • 기본값과 다른 재시도 제한이 필요함

나쁜 이유:

  • "그냥 안전을 위해" - 기본값이 이미 안전합니다
  • 액터가 무엇을 하는지 이해하지 못함 - 먼저 이해하십시오

3. 오류 처리: 감독 vs Try-Catch

Try-Catch를 사용해야 하는 경우 (대부분의 경우)

다음과 같은 경우 try-catch를 사용하십시오:

  • 실패가 예상됨 (네트워크 시간 초과, 잘못된 입력, 외부 서비스 중단)
  • 예외가 발생한 정확한 이유를 알고 있음
  • 정상적으로 처리할 수 있음 (재시도, 오류 응답 반환, 로깅 후 계속)
  • 재시작이 도움이 되지 않음 (동일한 오류가 다시 발생)
public class RssFeedPollerActor : ReceiveActor
{
    public RssFeedPollerActor()
    {
        ReceiveAsync<PollFeed>(async msg =>
        {
            try
            {
                var feed = await _httpClient.GetStringAsync(msg.FeedUrl);
                var items = ParseFeed(feed);
                // 항목 처리...
            }
            catch (HttpRequestException ex)
            {
                // 예상된 실패 - 로깅 후 재시도 예약
                _log.Warning("피드 {Url}을(를) 사용할 수 없음: {Error}", msg.FeedUrl, ex.Message);
                Context.System.Scheduler.ScheduleTellOnce(
                    TimeSpan.FromMinutes(5), Self, msg, Self);
            }
            catch (XmlException ex)
            {
                // 잘못된 피드 형식 - 로깅 후 불량으로 표시
                _log.Error("피드 {Url}의 형식이 잘못되었음: {Error}", msg.FeedUrl, ex.Message);
                Sender.Tell(new FeedPollResult.InvalidFormat(msg.FeedUrl));
            }
        });
    }
}

감독이 처리하도록 해야 하는 경우

다음과 같은 경우 예외를 전파(감독 트리거)하십시오:

  • 예외가 발생한 원인을 전혀 모름
  • 액터의 상태가 손상되었을 수 있음
  • 재시작이 도움이 될 것임 (새 상태, 리소스 재연결)
  • 프로그래밍 오류임 (잘못된 논리로 인한 NullReferenceException, InvalidOperationException)

안티 패턴: 알 수 없는 예외 삼키기

// 잘못된 예: 예외를 삼키면 문제가 숨겨집니다.
catch (Exception ex)
{
    _log.Error(ex, "작업 처리 중 오류 발생");
    // 액터가 잠재적으로 손상된 상태로 계속 실행됩니다.
}

// 올바른 예: 알려진 예외는 처리하고, 알 수 없는 예외는 전파합니다.
catch (HttpRequestException ex)
{
    // 알려진 예상 실패 - 정상적으로 처리
    _log.Warning("HTTP 요청 실패: {Error}", ex.Message);
    Sender.Tell(new WorkResult.TransientFailure());
}
// 알 수 없는 예외는 감독으로 전파됩니다.

4. Props vs DependencyResolver

일반 Props를 사용해야 하는 경우

다음과 같은 경우 Props.Create()를 사용하십시오:

  • 액터가 IServiceProvider 또는 IRequiredActor<T>를 필요로 하지 않음
  • 모든 종속성을 생성자를 통해 전달할 수 있음
  • 액터가 간단하고 자체 포함됨
// DI가 필요 없는 간단한 액터
public static Props Props(PostId postId, IPostWriteStore store)
    => Akka.Actor.Props.Create(() => new PostEngagementActor(postId, store));

DependencyResolver를 사용해야 하는 경우

다음과 같은 경우 resolver.Props<T>()를 사용하십시오:

  • 액터가 범위 서비스를 생성하기 위해 IServiceProvider가 필요함
  • 액터가 다른 액터에 대한 참조를 얻기 위해 IRequiredActor<T>를 사용함
  • 액터가 이미 DI 컨테이너에 있는 많은 종속성을 가짐
// DI로 등록
builder.WithActors((system, registry, resolver) =>
{
    var actor = system.ActorOf(resolver.Props<OrderProcessorActor>(), "order-processor");
    registry.Register<OrderProcessorActor>(actor);
});

원격 배포 고려 사항

원격 배포는 거의 필요하지 않습니다. 원격 배포를 하지 않는다면(아마 하지 않을 것입니다):

  • Props.Create(() => new Actor(...))를 클로저와 함께 사용해도 괜찮습니다.
  • "직렬화 문제" 경고는 적용되지 않습니다.

대부분의 애플리케이션에서는 원격 배포 대신 클러스터 샤딩을 사용하십시오. 자동으로 분산을 처리합니다.


5. 작업 분산 패턴

백그라운드 작업(RSS 피드, 이메일 전송 등)이 많을 때 한 번에 모두 처리하지 마십시오. 이는 떼밀기 문제(Thundering Herd Problem)를 유발합니다.

이 문제를 해결하는 세 가지 패턴:

  1. 데이터베이스 기반 작업 큐 - FOR UPDATE SKIP LOCKED를 사용하여 자연스러운 노드 간 분산 구현
  2. Akka.Streams 속도 제한 - 단일 노드 내 처리 속도 제한
  3. 내구성 있는 큐(아웃박스 패턴) - 데이터베이스 기반 아웃박스로 안정적인 처리 보장

전체 코드 샘플은 work-distribution-patterns.md를 참조하십시오.


6. 일반적인 실수 요약

실수잘못된 이유수정
노드 간 pub/sub에 EventStream 사용EventStream은 로컬 전용입니다DistributedPubSub 사용
액터를 "보호"하기 위해 감독 정의감독은 자식을 보호합니다계층 구조 이해
모든 예외 캐치버그를 숨기고 상태를 손상시킵니다예상된 오류만 캐치
항상 DependencyResolver 사용불필요한 복잡성을 추가합니다가능하면 일반 Props 사용
모든 백그라운드 작업을 한 번에 처리떼밀기, 리소스 고갈데이터베이스 큐 + 속도 제한 사용
예상된 실패에 대해 예외 발생불필요한 재시작을 유발합니다결과 유형 반환, 메시징 사용

7. 빠른 참조

통신 패턴 결정 트리

액터 간 통신이 필요합니까?
├── 동일 프로세스만? -> EventStream 괜찮음
├── 클러스터 노드 간?
│   ├── 점대점? -> ActorSelection 또는 알려진 IActorRef 사용
│   └── 게시/구독? -> DistributedPubSub 사용
└── 외부 시스템으로 발사 후 망각? -> 아웃박스 패턴 고려

오류 처리 결정 트리

액터에서 예외 발생?
├── 예상된 실패(HTTP 시간 초과, 잘못된 입력)?
│   └── try-catch, 정상적으로 처리, 계속
├── 상태가 손상되었을 수 있음?
│   └── 감독이 재시작하게 함
├── 원인 불명?
│   └── 감독이 재시작하게 함
└── 프로그래밍 오류(null 참조, 잘못된 논리)?
    └── 감독이 재시작하게 하고 버그 수정

Props 결정 트리

액터 Props 생성?
├── 액터가 IServiceProvider를 필요로 합니까?
│   └── resolver.Props<T>() 사용
├── 액터가 IRequiredActor<T>를 필요로 합니까?
│   └── resolver.Props<T>() 사용
├── 생성자 매개변수가 있는 간단한 액터?
│   └── Props.Create(() => new Actor(...)) 사용
└── 원격 배포가 필요합니까?
    └── 아마 아닙니다 - 대신 클러스터 샤딩 사용

8. 클러스터/로컬 모드 추상화

클러스터 프로덕션 환경과 로컬/테스트 환경 모두에서 실행해야 하는 애플리케이션의 경우 추상화 패턴을 사용하여 구현 간에 전환하십시오:

  • AkkaExecutionMode 열거형 - 사용할 구현 제어(LocalTest vs Clustered)
  • GenericChildPerEntityParent - 동일한 IMessageExtractor를 사용하여 로컬에서 샤딩 동작 모방
  • IPubSubMediator - 교체 가능한 로컬/클러스터 구현을 위해 DistributedPubSub 추상화

전체 구현 코드는 cluster-local-abstractions.md를 참조하십시오.


9. 액터 로깅

ILoggingAdapter 사용, ILogger 사용 안 함

액터에서는 DI로 주입된 ILogger<T> 대신 Context.GetLogger()의 ILoggingAdapter를 사용하십시오:

public class MyActor : ReceiveActor
{
    private readonly ILoggingAdapter _log = Context.GetLogger();

    public MyActor()
    {
        Receive<MyMessage>(msg =>
        {
            _log.Info("사용자 {UserId}에 대한 메시지 처리 중", msg.UserId);
            _log.Error(ex, "{MessageType} 처리 실패", msg.GetType().Name);
        });
    }
}

ILoggingAdapter를 사용해야 하는 이유:

  • Akka의 로깅 파이프라인 및 감독과 통합됨
  • v1.5.57부터 의미론적/구조적 로깅 지원
  • 메서드 이름: Info(), Debug(), Warning(), Error() (Log* 변형 아님)
  • DI 필요 없음 - 액터 컨텍스트에서 직접 가져옴

ILogger를 액터에 주입하지 마십시오 - Akka의 로깅 인프라를 우회합니다.

의미론적 로깅 (v1.5.57+)

// 더 나은 로그 집계 및 쿼리를 위한 명명된 자리 표시자
_log.Info("주문 {OrderId}이(가) 고객 {CustomerId}에 대해 처리됨", order.Id, order.CustomerId);

// 위치 기반보다 명명된 자리 표시자 선호
// 좋음: {OrderId}, {CustomerId}
// 피해야 할 것: {0}, {1}

10. CancellationToken으로 비동기 작업 관리

액터가 PipeTo를 통해 비동기 작업을 시작할 때, 제대로 관리되지 않으면 해당 작업이 액터보다 오래 지속될 수 있습니다. 주요 방법:

  • PostStop의 액터 CTS - PostStop()에서 항상 취소 및 해제
  • 작업당 새 CTS - 새 작업을 시작하기 전에 이전 작업 취소
  • 모든 곳에 토큰 전달 - EF Core 쿼리, HTTP 호출 등
  • 시간 초과를 위한 연결된 CTS - 외부 호출은 중단을 방지하기 위해 짧은 시간 초과 설정
  • 정상적인 처리 - catch 블록에서 시간 초과와 종료 구분

전체 구현 코드는 async-cancellation-patterns.md를 참조하십시오.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

marketplace-publishing

Workflow for publishing skills and agents to the dotnet-skills Claude Code marketplace. Covers adding new content, updating plugin.json, validation, and release tagging.

관련 스킬