Java 백엔드 개발자가 C#과 .NET 생태계를 학습하며 만든 프로젝트 모음입니다.
약 1년 9개월간 Java/Spring 기반으로 백엔드를 개발했습니다. 분산 트랜잭션, 동시성 제어, 비동기 아키텍처, 모니터링 시스템 구축을 경험했지만, 그 과정에서 사용한 도구들(Redis 분산 락, SQS/SNS, Outbox 패턴)은 각각 별도로 조립해야 하는 것들이었습니다.
C#과 .NET은 다른 접근을 취합니다. async/await는 스레드 모델 자체가 다르고, Orleans는 분산 시스템의 어려운 부분을 런타임이 흡수합니다. 튜토리얼을 따라 하는 대신, 기존 경험과 대조하며 "왜 이렇게 설계되었는지"를 확인하는 방식으로 학습했습니다.
TCP Echo Server — async/await를 밑바닥부터
표준 라이브러리만으로 비동기 TCP 에코 서버를 구현했습니다.
- 동시 접속 처리:
await HandleClientAsync()vs_ = HandleClientAsync()의 차이를 두 클라이언트로 직접 검증 - fire-and-forget의 예외 소실을 실험으로 확인하고 try/catch로 격리
- 디버거로 async 메서드가 상태 기계로 변환되는 흐름 추적
IDisposable, 버퍼 관리, TCP 스트림의 메시지 경계 문제
핵심 이해: Task는 스레드가 아니라 미래에 완료될 작업이며,
I/O 대기 중에는 어떤 스레드도 점유되지 않는다.
Orleans Counter Demo — 가상 액터 모델
Microsoft Orleans의 grain에 동시에 1000번의 증가 요청을 던져, 락 없이 정합성이 유지되는 것을 확인했습니다.
이전 회사에서 프로모션 중복 지급 사고를 Redis 분산 락으로 해결한 경험이 있습니다. Orleans는 같은 문제를 락이 아니라 액터 격리로 풉니다. 락으로 강제하던 "사용자 단위 순차 처리"가 grain에서는 모델 자체에 내장되어 있습니다.
핵심 이해: grain은 생성하는 것이 아니라 ID로 호출하는 가상 액터이며, 메시지를 한 번에 하나씩 처리하므로 경합 조건이 원천적으로 발생하지 않는다.
| Java/Spring에서 직접 구현한 것 | .NET / Orleans의 방식 |
|---|---|
| Redis 분산 락으로 사용자 ID 단위 동시성 제어 | grain 단일 스레드 실행으로 락 불필요 |
| Redis 캐시 + DB 이중 구조 | grain 상태가 메모리에 상주 |
| Outbox 패턴 + Lambda 보상 트랜잭션 | 런타임이 장애 감지 및 재활성화 처리 |
| SQS/SNS 기반 비동기 메시징 | grain 간 메시지 전달이 기본 모델 |
| 스레드풀 기반 블로킹 I/O | 상태 기계 기반 논블로킹 I/O |
직접 조립해서 만들었던 것들을 .NET 생태계는 상당 부분 기본 제공합니다. 그 문제들을 실제로 겪어봤기 때문에, 이 프레임워크들이 무엇을 해결해주는지 이해할 수 있었습니다.
- .NET 10 (net10.0), C# 14
System.Net.Sockets- Microsoft.Orleans.Server
- 외부 의존성 최소화 (표준 라이브러리 우선)
모두 학습용 프로토타입입니다. 프로덕션 수준의 구현이 아니며, 성능 수치를 측정하지 않았으므로 벤치마크 결과는 기재하지 않았습니다.
각 프로젝트의 README에 구현하지 않은 항목을 명시했습니다.
