[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-a185c8e4-e8cb-49d0-a689-3b60547d0bfd":3,"$fx2dWZDeFPf2M3QdOV4M2oMxGlH-kF-KlVqbbv084X4k":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"a185c8e4-e8cb-49d0-a689-3b60547d0bfd","event-store-design","设计和实现事件存储，用于事件源系统。在构建事件源基础设施、选择事件存储技术或实现事件持久化模式时使用。","cat_life_career","mod_other","sickn33,other","---\nname: event-store-design\ndescription: \"Design and implement event stores for event-sourced systems. Use when building event sourcing infrastructure, choosing event store technologies, or implementing event persistence patterns.\"\nrisk: unknown\nsource: community\ndate_added: \"2026-02-27\"\n---\n\n# Event Store Design\n\nComprehensive guide to designing event stores for event-sourced applications.\n\n## Do not use this skill when\n\n- The task is unrelated to event store design\n- You need a different domain or tool outside this scope\n\n## Instructions\n\n- Clarify goals, constraints, and required inputs.\n- Apply relevant best practices and validate outcomes.\n- Provide actionable steps and verification.\n- If detailed examples are required, open `resources\u002Fimplementation-playbook.md`.\n\n## Use this skill when\n\n- Designing event sourcing infrastructure\n- Choosing between event store technologies\n- Implementing custom event stores\n- Optimizing event storage and retrieval\n- Setting up event store schemas\n- Planning for event store scaling\n\n## Core Concepts\n\n### 1. Event Store Architecture\n\n```\n┌─────────────────────────────────────────────────────┐\n│                    Event Store                       │\n├─────────────────────────────────────────────────────┤\n│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │\n│  │   Stream 1   │  │   Stream 2   │  │   Stream 3   │ │\n│  │ (Aggregate)  │  │ (Aggregate)  │  │ (Aggregate)  │ │\n│  ├─────────────┤  ├─────────────┤  ├─────────────┤ │\n│  │ Event 1     │  │ Event 1     │  │ Event 1     │ │\n│  │ Event 2     │  │ Event 2     │  │ Event 2     │ │\n│  │ Event 3     │  │ ...         │  │ Event 3     │ │\n│  │ ...         │  │             │  │ Event 4     │ │\n│  └─────────────┘  └─────────────┘  └─────────────┘ │\n├─────────────────────────────────────────────────────┤\n│  Global Position: 1 → 2 → 3 → 4 → 5 → 6 → ...     │\n└─────────────────────────────────────────────────────┘\n```\n\n### 2. Event Store Requirements\n\n| Requirement       | Description                        |\n| ----------------- | ---------------------------------- |\n| **Append-only**   | Events are immutable, only appends |\n| **Ordered**       | Per-stream and global ordering     |\n| **Versioned**     | Optimistic concurrency control     |\n| **Subscriptions** | Real-time event notifications      |\n| **Idempotent**    | Handle duplicate writes safely     |\n\n## Technology Comparison\n\n| Technology       | Best For                  | Limitations                      |\n| ---------------- | ------------------------- | -------------------------------- |\n| **EventStoreDB** | Pure event sourcing       | Single-purpose                   |\n| **PostgreSQL**   | Existing Postgres stack   | Manual implementation            |\n| **Kafka**        | High-throughput streaming | Not ideal for per-stream queries |\n| **DynamoDB**     | Serverless, AWS-native    | Query limitations                |\n| **Marten**       | .NET ecosystems           | .NET specific                    |\n\n## Templates\n\n### Template 1: PostgreSQL Event Store Schema\n\n```sql\n-- Events table\nCREATE TABLE events (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    stream_id VARCHAR(255) NOT NULL,\n    stream_type VARCHAR(255) NOT NULL,\n    event_type VARCHAR(255) NOT NULL,\n    event_data JSONB NOT NULL,\n    metadata JSONB DEFAULT '{}',\n    version BIGINT NOT NULL,\n    global_position BIGSERIAL,\n    created_at TIMESTAMPTZ DEFAULT NOW(),\n\n    CONSTRAINT unique_stream_version UNIQUE (stream_id, version)\n);\n\n-- Index for stream queries\nCREATE INDEX idx_events_stream_id ON events(stream_id, version);\n\n-- Index for global subscription\nCREATE INDEX idx_events_global_position ON events(global_position);\n\n-- Index for event type queries\nCREATE INDEX idx_events_event_type ON events(event_type);\n\n-- Index for time-based queries\nCREATE INDEX idx_events_created_at ON events(created_at);\n\n-- Snapshots table\nCREATE TABLE snapshots (\n    stream_id VARCHAR(255) PRIMARY KEY,\n    stream_type VARCHAR(255) NOT NULL,\n    snapshot_data JSONB NOT NULL,\n    version BIGINT NOT NULL,\n    created_at TIMESTAMPTZ DEFAULT NOW()\n);\n\n-- Subscriptions checkpoint table\nCREATE TABLE subscription_checkpoints (\n    subscription_id VARCHAR(255) PRIMARY KEY,\n    last_position BIGINT NOT NULL DEFAULT 0,\n    updated_at TIMESTAMPTZ DEFAULT NOW()\n);\n```\n\n### Template 2: Python Event Store Implementation\n\n```python\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\nfrom typing import Any, Optional, List\nfrom uuid import UUID, uuid4\nimport json\nimport asyncpg\n\n@dataclass\nclass Event:\n    stream_id: str\n    event_type: str\n    data: dict\n    metadata: dict = field(default_factory=dict)\n    event_id: UUID = field(default_factory=uuid4)\n    version: Optional[int] = None\n    global_position: Optional[int] = None\n    created_at: datetime = field(default_factory=datetime.utcnow)\n\n\nclass EventStore:\n    def __init__(self, pool: asyncpg.Pool):\n        self.pool = pool\n\n    async def append_events(\n        self,\n        stream_id: str,\n        stream_type: str,\n        events: List[Event],\n        expected_version: Optional[int] = None\n    ) -> List[Event]:\n        \"\"\"Append events to a stream with optimistic concurrency.\"\"\"\n        async with self.pool.acquire() as conn:\n            async with conn.transaction():\n                # Check expected version\n                if expected_version is not None:\n                    current = await conn.fetchval(\n                        \"SELECT MAX(version) FROM events WHERE stream_id = $1\",\n                        stream_id\n                    )\n                    current = current or 0\n                    if current != expected_version:\n                        raise ConcurrencyError(\n                            f\"Expected version {expected_version}, got {current}\"\n                        )\n\n                # Get starting version\n                start_version = await conn.fetchval(\n                    \"SELECT COALESCE(MAX(version), 0) + 1 FROM events WHERE stream_id = $1\",\n                    stream_id\n                )\n\n                # Insert events\n                saved_events = []\n                for i, event in enumerate(events):\n                    event.version = start_version + i\n                    row = await conn.fetchrow(\n                        \"\"\"\n                        INSERT INTO events (id, stream_id, stream_type, event_type,\n                                          event_data, metadata, version, created_at)\n                        VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n                        RETURNING global_position\n                        \"\"\",\n                        event.event_id,\n                        stream_id,\n                        stream_type,\n                        event.event_type,\n                        json.dumps(event.data),\n                        json.dumps(event.metadata),\n                        event.version,\n                        event.created_at\n                    )\n                    event.global_position = row['global_position']\n                    saved_events.append(event)\n\n                return saved_events\n\n    async def read_stream(\n        self,\n        stream_id: str,\n        from_version: int = 0,\n        limit: int = 1000\n    ) -> List[Event]:\n        \"\"\"Read events from a stream.\"\"\"\n        async with self.pool.acquire() as conn:\n            rows = await conn.fetch(\n                \"\"\"\n                SELECT id, stream_id, event_type, event_data, metadata,\n                       version, global_position, created_at\n                FROM events\n                WHERE stream_id = $1 AND version >= $2\n                ORDER BY version\n                LIMIT $3\n                \"\"\",\n                stream_id, from_version, limit\n            )\n            return [self._row_to_event(row) for row in rows]\n\n    async def read_all(\n        self,\n        from_position: int = 0,\n        limit: int = 1000\n    ) -> List[Event]:\n        \"\"\"Read all events globally.\"\"\"\n        async with self.pool.acquire() as conn:\n            rows = await conn.fetch(\n                \"\"\"\n                SELECT id, stream_id, event_type, event_data, metadata,\n                       version, global_position, created_at\n                FROM events\n                WHERE global_position > $1\n                ORDER BY global_position\n                LIMIT $2\n                \"\"\",\n                from_position, limit\n            )\n            return [self._row_to_event(row) for row in rows]\n\n    async def subscribe(\n        self,\n        subscription_id: str,\n        handler,\n        from_position: int = 0,\n        batch_size: int = 100\n    ):\n        \"\"\"Subscribe to all events from a position.\"\"\"\n        # Get checkpoint\n        async with self.pool.acquire() as conn:\n            checkpoint = await conn.fetchval(\n                \"\"\"\n                SELECT last_position FROM subscription_checkpoints\n                WHERE subscription_id = $1\n                \"\"\",\n                subscription_id\n            )\n            position = checkpoint or from_position\n\n        while True:\n            events = await self.read_all(position, batch_size)\n            if not events:\n                await asyncio.sleep(1)  # Poll interval\n                continue\n\n            for event in events:\n                await handler(event)\n                position = event.global_position\n\n            # Save checkpoint\n            async with self.pool.acquire() as conn:\n                await conn.execute(\n                    \"\"\"\n                    INSERT INTO subscription_checkpoints (subscription_id, last_position)\n                    VALUES ($1, $2)\n                    ON CONFLICT (subscription_id)\n                    DO UPDATE SET last_position = $2, updated_at = NOW()\n                    \"\"\",\n                    subscription_id, position\n                )\n\n    def _row_to_event(self, row) -> Event:\n        return Event(\n            event_id=row['id'],\n            stream_id=row['stream_id'],\n            event_type=row['event_type'],\n            data=json.loads(row['event_data']),\n            metadata=json.loads(row['metadata']),\n            version=row['version'],\n            global_position=row['global_position'],\n            created_at=row['created_at']\n        )\n\n\nclass ConcurrencyError(Exception):\n    \"\"\"Raised when optimistic concurrency check fails.\"\"\"\n    pass\n```\n\n### Template 3: EventStoreDB Usage\n\n```python\nfrom esdbclient import EventStoreDBClient, NewEvent, StreamState\nimport json\n\n# Connect\nclient = EventStoreDBClient(uri=\"esdb:\u002F\u002Flocalhost:2113?tls=false\")\n\n# Append events\ndef append_events(stream_name: str, events: list, expected_revision=None):\n    new_events = [\n        NewEvent(\n            type=event['type'],\n            data=json.dumps(event['data']).encode(),\n            metadata=json.dumps(event.get('metadata', {})).encode()\n        )\n        for event in events\n    ]\n\n    if expected_revision is None:\n        state = StreamState.ANY\n    elif expected_revision == -1:\n        state = StreamState.NO_STREAM\n    else:\n        state = expected_revision\n\n    return client.append_to_stream(\n        stream_name=stream_name,\n        events=new_events,\n        current_version=state\n    )\n\n# Read stream\ndef read_stream(stream_name: str, from_revision: int = 0):\n    events = client.get_stream(\n        stream_name=stream_name,\n        stream_position=from_revision\n    )\n    return [\n        {\n            'type': event.type,\n            'data': json.loads(event.data),\n            'metadata': json.loads(event.metadata) if event.metadata else {},\n            'stream_position': event.stream_position,\n            'commit_position': event.commit_position\n        }\n        for event in events\n    ]\n\n# Subscribe to all\nasync def subscribe_to_all(handler, from_position: int = 0):\n    subscription = client.subscribe_to_all(commit_position=from_position)\n    async for event in subscription:\n        await handler({\n            'type': event.type,\n            'data': json.loads(event.data),\n            'stream_id': event.stream_name,\n            'position': event.commit_position\n        })\n\n# Category projection ($ce-Category)\ndef read_category(category: str):\n    \"\"\"Read all events for a category using system projection.\"\"\"\n    return read_stream(f\"$ce-{category}\")\n```\n\n### Template 4: DynamoDB Event Store\n\n```python\nimport boto3\nfrom boto3.dynamodb.conditions import Key\nfrom datetime import datetime\nimport json\nimport uuid\n\nclass DynamoEventStore:\n    def __init__(self, table_name: str):\n        self.dynamodb = boto3.resource('dynamodb')\n        self.table = self.dynamodb.Table(table_name)\n\n    def append_events(self, stream_id: str, events: list, expected_version: int = None):\n        \"\"\"Append events with conditional write for concurrency.\"\"\"\n        with self.table.batch_writer() as batch:\n            for i, event in enumerate(events):\n                version = (expected_version or 0) + i + 1\n                item = {\n                    'PK': f\"STREAM#{stream_id}\",\n                    'SK': f\"VERSION#{version:020d}\",\n                    'GSI1PK': 'EVENTS',\n                    'GSI1SK': datetime.utcnow().isoformat(),\n                    'event_id': str(uuid.uuid4()),\n                    'stream_id': stream_id,\n                    'event_type': event['type'],\n                    'event_data': json.dumps(event['data']),\n                    'version': version,\n                    'created_at': datetime.utcnow().isoformat()\n                }\n                batch.put_item(Item=item)\n        return events\n\n    def read_stream(self, stream_id: str, from_version: int = 0):\n        \"\"\"Read events from a stream.\"\"\"\n        response = self.table.query(\n            KeyConditionExpression=Key('PK').eq(f\"STREAM#{stream_id}\") &\n                                  Key('SK').gte(f\"VERSION#{from_version:020d}\")\n        )\n        return [\n            {\n                'event_type': item['event_type'],\n                'data': json.loads(item['event_data']),\n                'version': item['version']\n            }\n            for item in response['Items']\n        ]\n\n# Table definition (CloudFormation\u002FTerraform)\n\"\"\"\nDynamoDB Table:\n  - PK (Partition Key): String\n  - SK (Sort Key): String\n  - GSI1PK, GSI1SK for global ordering\n\nCapacity: On-demand or provisioned based on throughput needs\n\"\"\"\n```\n\n## Best Practices\n\n### Do's\n\n- **Use stream IDs that include aggregate type** - `Order-{uuid}`\n- **Include correlation\u002Fcausation IDs** - For tracing\n- **Version events from day one** - Plan for schema evolution\n- **Implement idempotency** - Use event IDs for deduplication\n- **Index appropriately** - For your query patterns\n\n### Don'ts\n\n- **Don't update or delete events** - They're immutable facts\n- **Don't store large payloads** - Keep events small\n- **Don't skip optimistic concurrency** - Prevents data corruption\n- **Don't ignore backpressure** - Handle slow consumers\n\n## Resources\n\n- [EventStoreDB](https:\u002F\u002Fwww.eventstore.com\u002F)\n- [Marten Events](https:\u002F\u002Fmartendb.io\u002Fevents\u002F)\n- [Event Sourcing Pattern](https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fazure\u002Farchitecture\u002Fpatterns\u002Fevent-sourcing)\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,247,108,"2026-05-16 13:17:13",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"其他","other","mdi-page-next-outline","其他类型Skill",5,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"职场发展","career","mdi-briefcase-outline","面试准备、简历优化、职业规划",4,575,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"cd348e04-f0e5-423f-ac20-3c9bc917433e","1.0.0","event-store-design.zip",5239,"uploads\u002Fskills\u002Fa185c8e4-e8cb-49d0-a689-3b60547d0bfd\u002Fevent-store-design.zip","2f72b6ed2f1d4e1b7e98d7464ba017191878b29142a025ec98894cf1ba6f8f27","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":16334},{\"path\":\"resources\u002Fimplementation-playbook.md\",\"isDirectory\":false,\"size\":746}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]