액터모델의 메일박스로 통해 전달되는 이벤트들은 다양한 DB장치를 이용해 영속화가 가능하며 상태프로그래밍을 할수 있습니다. |

데이터베이스는 지루하면 안된다라는 슬로건을 내건 모던 DB
| 항목 | 설명 |
|---|---|
| Document Store | JSON 기반의 문서 저장 (MongoDB처럼) |
| Full-Text Search 내장 | Lucene 기반 검색엔진 포함 (Elasticsearch 대체 가능) |
| Graph-Like Traversal 지원 | Include, Load, RelatedDocuments 로 Graph traversal 흉내 가능 |
| 벡터 검색 (Vector Search) | 6.0 이상 버전에서 Vector search 지원 (Preview → Stable 예정) |
| ACID 트랜잭션 지원 | NoSQL 중 드물게 단일 DB 내 ACID 지원 |
| 자동 인덱싱/쿼리 최적화 | 쿼리 기반으로 자동 인덱싱 생성 |
| Change Vector / ETL 기능 내장 | 다른 Raven 클러스터 또는 외부 시스템으로 데이터 복제 가능 |
| 클라우드 + 온프렘 지원 | 다양한 배포 환경 대응 |
| Sharding + Replication | 분산 구조 대응 가능 (Sharded DB) |
| 기존 시스템 | RavenDB로 대체 가능 여부 | 설명 |
|---|---|---|
| MongoDB (Document DB) | ✅ 완전 대체 | JSON 기반 문서 저장, 컬렉션 → 문서 분리 모델 |
| Elasticsearch | ✅ 부분 대체 | Full-text 검색 지원, 복잡한 분석쿼리는 제한적이나 일반 검색에는 충분 |
| Neo4j (Graph DB) | ⚠️ 간단한 관계 트래버설은 가능 | 명시적 Graph 모델링은 어려움 (복잡한 네트워크 분석에는 부적합) |
| Vector DB (예: Weaviate, Milvus) | ✅ 단순 벡터 검색은 대체 가능 | 다차원 벡터 검색 API 제공, 모델링+쿼리 결합 쉬움 |
| RDB (CRUD/정형) | ⚠️ 단순 CRUD는 가능, 복잡한 조인과 트랜잭션은 제한적 | 정형 테이블 기반보다는 문서 중심 모델 필요 |
version: '3.8'
services:
ravendb:
image: ravendb/ravendb:ubuntu-latest
container_name: ravendb
ports:
- "9000:8080"
environment:
- RAVEN_Setup_Mode=None
- RAVEN_License_Eula_Accepted=true
volumes:
- ravendb_data:/ravendb/data
- ravendb_logs:/ravendb/logs
volumes:
ravendb_data:
ravendb_logs: |





몽고DB와 유사하게 스키마리스 DB이기때문에 DDL코드가 필요로 하지 않습니다.
public class Member
{
public string Id { get; set; } // RavenDB는 기본적으로 Id를 문서 키로 사용
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
public class MemberRepository
{
private readonly IDocumentStore _store;
public MemberRepository(IDocumentStore store)
{
_store = store ?? throw new ArgumentNullException(nameof(store));
}
public void AddMember(Member member)
{
using (var session = _store.OpenSession())
{
session.Store(member);
session.SaveChanges();
}
}
public Member GetMemberById(string id)
{
using (var session = _store.OpenSession())
{
return session.Load<Member>(id);
}
}
public void UpdateMember(Member member)
{
using (var session = _store.OpenSession())
{
var existingMember = session.Load<Member>(member.Id);
if (existingMember != null)
{
existingMember.Name = member.Name;
existingMember.Email = member.Email;
existingMember.Age = member.Age;
session.SaveChanges();
}
}
}
public void DeleteMember(string id)
{
using (var session = _store.OpenSession())
{
var member = session.Load<Member>(id);
if (member != null)
{
session.Delete(member);
session.SaveChanges();
}
}
}
} |
public class MemberRepositoryTest : TestKitXunit
{
private readonly IDocumentStore _store;
private readonly MemberRepository _repository;
public MemberRepositoryTest(ITestOutputHelper output) : base(output)
{
// RavenDB 임베디드 서버 초기화
_store = new DocumentStore
{
Urls = new[] { "http://localhost:9000" }, // 로컬 RavenDB URL
Database = "net-core-labs"
};
_store.Initialize();
// MemberRepository 초기화
_repository = new MemberRepository(_store);
}
[Fact]
public void AddMember_ShouldAddMemberSuccessfully()
{
// Arrange
var member = new Member
{
Name = "John Doe",
Email = "john.doe@example.com",
Age = 30
};
// Act
_repository.AddMember(member);
// Assert
var retrievedMember = _repository.GetMemberById(member.Id);
Assert.NotNull(retrievedMember);
Assert.Equal("John Doe", retrievedMember.Name);
}
[Fact]
public void UpdateMember_ShouldUpdateMemberSuccessfully()
{
// Arrange
var member = new Member
{
Name = "Jane Doe",
Email = "jane.doe@example.com",
Age = 25
};
_repository.AddMember(member);
// Act
member.Age = 26;
_repository.UpdateMember(member);
// Assert
var updatedMember = _repository.GetMemberById(member.Id);
Assert.NotNull(updatedMember);
Assert.Equal(26, updatedMember.Age);
}
[Fact]
public void DeleteMember_ShouldDeleteMemberSuccessfully()
{
// Arrange
var member = new Member
{
Name = "Mark Smith",
Email = "mark.smith@example.com",
Age = 40
};
_repository.AddMember(member);
// Act
_repository.DeleteMember(member.Id);
// Assert
var deletedMember = _repository.GetMemberById(member.Id);
Assert.Null(deletedMember);
}
} |


CRUD만 이용하려고 RavenDB를 선택한것은 아니고~ Akka.net의 Persist기능과 연동되어
이벤트 소싱을 액터모델의 Persitent 기능과 함께 심플하게 이용할수 있기때문입니다.

RavenDB가 Akka.net의 Persitence 기능을 지원하며 이 코드를 이해하기전 이벤트 소싱패턴을 액터모델을 함께 이용했을때 특징을 먼저 살펴보고 작동가능한 구현된 샘플코드도 유닛테스트를 통해 살펴보겠습니다.
Akka.NET의 액터모델은 CQRS (Command Query Responsibility Segregation) 및 이벤트 소싱 (Event Sourcing) 패턴을 구현하기에 매우 적합한 구조를 제공합니다. 특히 Akka.Persistence 모듈과의 연계를 통해 상태 저장과 복구, 그리고 이벤트 기반 모델링이 가능해집니다. 아래에 그 기능과 장점을 정리해드립니다.
| 구성요소 | 설명 |
|---|---|
PersistentActor | 이벤트를 저장하고 재생할 수 있는 상태 유지 액터 |
Snapshotting | 빠른 복구를 위해 특정 시점의 상태를 저장 |
Event Journal | 모든 상태 변화(event)를 append-only 로그로 저장 |
Read Model Actor | 쿼리에 최적화된 projection을 담당 |
Command Handler Actor | 명령(Command)을 받아 이벤트로 전환 및 persistence 수행 |
2. CQRS(이벤트 소싱) 다이어그램

상태 격리: 각 액터는 고유 상태를 가지며 병렬 처리에 유리함.
비동기 메시지 기반 처리: 병목 없이 고성능 분산 처리 가능.
자연스러운 도메인 분리: 액터 자체가 DDD의 Aggregate Root 역할을 하기에 적합.
이벤트 이력 관리: 모든 상태 변화가 이벤트로 저장되므로 과거 상태 추적 가능 (감사, 재처리).
읽기/쓰기 분리: 쿼리와 명령이 분리되어 성능 최적화 가능.
스냅샷 사용 가능: 복구 속도 향상.
스케일 아웃 용이: 각 액터가 독립적으로 배포/스케일링 가능.
Akka.Cluster와 연계 시 분산 시스템 구성도 가능
Akka.Remote로 마이크로서비스 간 통신에도 활용 가능
Pluggable Journal Backend: SQL, NoSQL, 이벤트 스토어 등 다양한 저장소와 연동 가능
고객 주문 시스템: OrderActor가 PlaceOrderCommand를 받고 OrderPlacedEvent를 생성 및 저장
챗봇 세션 관리: SessionActor가 메시지를 이벤트로 저장하여 상태 기반 대화 흐름 관리
금융 거래 기록: AccountActor가 Withdrawn, Deposited 이벤트를 기록하여 완전한 거래 이력 확보
여기서 실험된 작동기능은 다음 저장소에서 실행및 수행가능하며 Akka.net 기반의 액터모델을 중심으로한 다양한 기능들을 확인할수 있습니다.