# Mongoengine Read-Write Router

A MongoDB read-write separation solution for mongoengine ODM, inspired by the [django-rw-router](https://github.com/pydtools/django-rw-router) architecture.

This library routes write operations to the primary database and read operations to replica set members, with configurable consistency strategies.

## Features

- **Automatic read-write routing** - Writes go to primary, reads go to replicas
- **Multiple consistency strategies** - random, transaction-aware, consistent hashing
- **Read-after-write consistency** - Ensures you see your own writes
- **Request context tracking** - Thread-safe context using `contextvars`
- **Web framework integration** - Middleware for FastAPI, Flask, and Django
- **Force primary reads** - Explicit control when you need fresh data
- **Write operation tracking** - Automatic context updates on writes

## Installation

```bash
pip install mongoengine-rw-router
```

## Quick Start

```python
from mongoengine import connect, Document, StringField, IntField
from mongoengine_rw_router import (
    RwDocumentMixin,
    configure,
    MongoRouterMiddleware,
)

# 1. Set up connections
connect(db="myapp", alias="default", host="mongodb://primary:27017")
connect(
    db="myapp",
    alias="replica1",
    host="mongodb://replica1:27017",
    read_preference=ReadPreference.SECONDARY_PREFERRED,
)

# 2. Configure router
configure(
    primary_alias="default",
    replica_aliases=["replica1"],
    strategy="transaction_aware",
)

# 3. Define model with routing
class User(RwDocumentMixin, Document):
    name = StringField()
    email = StringField()
    age = IntField()

    meta = {"db_alias": "default"}

# 4. Use it!
user = User.objects.first()  # Reads from replica
user.age = 25
user.save()  # Writes to primary
```

## Strategy Options

| 策略 | 配置 | 一致性 |
|------|------|--------|
| `random` | 基础读写分离 | 最终一致 (Eventual Consistency) |
| `transaction_aware` | 事务内 + 写后读主库 | Read Your Writes |
| `consistent_hash` | 同一 user_id 固定从库 | Monotonic Reads / Consistent Prefix |

## 一致性场景 (DDIA)

与 [django-rw-router](https://github.com/pydtools/django-rw-router) 一致，支持 DDIA 第四章复制滞后导致的四种读一致性问题：

| 场景 | 含义 | 推荐配置 |
|------|------|----------|
| **Read Your Writes** | 写入后立即读可见 | `strategy="transaction_aware"` + `read_after_write_consistency=True` + `transaction_read_primary=True` |
| **Monotonic Reads** | 同一用户不出现时光倒流 | `strategy="consistent_hash"` + 中间件注入 `user_id` |
| **Consistent Prefix** | 有序读取不出现乱序 | `strategy="consistent_hash"`（同 Monotonic Reads） |
| **Eventual Consistency** | 接受复制滞后 | `strategy="random"` |

### 配置示例

```python
# Read Your Writes（推荐，默认）
configure(
    primary_alias="default",
    replica_aliases=["replica1", "replica2"],
    strategy="transaction_aware",
    read_after_write_consistency=True,
    transaction_read_primary=True,
)

# Monotonic Reads / Consistent Prefix（需中间件注入 user_id）
configure(
    strategy="consistent_hash",
    hash_virtual_nodes=40,
)
app.add_middleware(MongoRouterMiddleware, user_id_attr="user.id")

# Eventual Consistency
configure(strategy="random")
```

### 强制主库读

```python
# 方式 1：primary_queryset
user = User.primary_queryset().first()

# 方式 2：force_primary 链式调用
user = User.objects.force_primary().first()

# 方式 3：force_primary 上下文
from mongoengine_rw_router import force_primary
with force_primary():
    user = User.objects.first()
```

## License

MIT

## Credits

Inspired by [django-rw-router](https://github.com/pydtools/django-rw-router)
