diff --git a/.agents/skills/add-persistence-or-serializer/SKILL.md b/.agents/skills/add-persistence-or-serializer/SKILL.md deleted file mode 100644 index 1c14fff98..000000000 --- a/.agents/skills/add-persistence-or-serializer/SKILL.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -name: add-persistence-or-serializer -description: "Use when: adding a new persistence provider, adding a new serializer, reviewing an existing serializer implementation, wiring up a new storage backend, or checking whether a persistence/serialization component follows NEventStore conventions." ---- - -# Add or Review a Persistence Provider / Serializer - -## When to invoke -- User asks to create a new persistence backend (e.g., MongoDB, PostgreSQL). -- User asks to create a new serializer (e.g., MessagePack, Protobuf). -- Code review of an existing provider or serializer against repository conventions. - -## Checklist - -### For a new **Serializer** - -1. **Implement `ISerialize`** (`src/NEventStore/Serialization/ISerialize.cs`): - ```csharp - public interface ISerialize - { - void Serialize(Stream output, T graph) where T : notnull; - T? Deserialize(Stream input); - } - ``` - -2. **Create a dedicated project** under `src/NEventStore.Serialization./`: - - Target `netstandard2.0` to stay consistent with the core library. - - Set `GenerateAssemblyInfo=false` (version is managed by GitVersion). - - Add `PackageId`, `Authors`, `Description`, `PackageTags` matching the existing `.csproj` files. - - Include `icon.png`, `license.txt`, and `Readme.md` as pack items pointing to the root files. - - Enable `GenerateDocumentationFile`, `IncludeSymbols`, `SymbolPackageFormat=snupkg`. - - Reference `NEventStore.Core.csproj`. - -3. **Write the wireup extension** in a class `SerializationWireupExtension`: - ```csharp - public static class SerializationWireupExtension - { - public static SerializationWireup UsingSerialization( - this PersistenceWireup wireup, - /* optional settings */) - { - return wireup.UsingCustomSerialization(new Serializer(/* ... */)); - } - } - ``` - - Return `SerializationWireup`, not `PersistenceWireup`, so `.Compress()` and `.EncryptWith()` remain available. - -4. **Known-types pattern**: if your serializer needs explicit type registration (like the JSON serializer does for `List` and `Dictionary`), accept them via a `params Type[]? knownTypes` parameter. - -5. **Logging**: inject `LogFactory.BuildLogger(typeof(MySerializer))` and guard every log call with `Logger.IsEnabled(LogLevel.X)` before formatting the message. Never use `Console.Write*`. - -6. **Tests**: create `src/NEventStore.Serialization..Tests/` using the same `DefineConstants=NUNIT` pattern. Run `NEventStore.Persistence.AcceptanceTests.SerializationTests` against the new serializer to verify round-trip correctness. - ---- - -### For a new **Persistence Provider** - -1. **Implement `IPersistStreams`** (`src/NEventStore/Persistence/IPersistStreams.cs`), which composes `IPersistStreamsSync` and `IPersistStreamsAsync`. Both sides must be implemented. - -2. **Create a dedicated project** (same conventions as a serializer project above). - -3. **Wireup extension** on `Wireup`, returning `PersistenceWireup`: - ```csharp - public static PersistenceWireup UsingPersistence(this Wireup wireup, /* connection options */) - { - wireup.Register(new PersistenceEngine(/* ... */)); - return new PersistenceWireup(wireup); - } - ``` - -4. **Acceptance tests**: reference `NEventStore.Persistence.AcceptanceTests` and provide a `PersistenceEngineFixture` that wires up your engine. All `PersistenceEngineConcern`-based tests must pass green against your implementation. - -5. **Thread safety**: `IPersistStreams` must be multi-thread safe. Use thread-safe collections or locking; document any remaining single-threaded constraints in XML doc comments. - -6. **`Initialize()` must be synchronous** and complete before the method returns. The wireup calls it during `Build()`. - -7. **Checkpoint tokens**: must be monotonically increasing and comparable. Do not use random GUIDs as checkpoint values. - ---- - -## Review Checklist -- [ ] `ISerialize` / `IPersistStreams` fully implemented (no `NotImplementedException` stubs left). -- [ ] Wireup extension returns correct `Wireup` subclass (`SerializationWireup` or `PersistenceWireup`). -- [ ] `GenerateAssemblyInfo=false` in `.csproj`. -- [ ] `icon.png`, `license.txt`, `Readme.md` included as pack items. -- [ ] All public types have XML doc comments. -- [ ] Nullable reference types respected (`?` annotations correct). -- [ ] `Guard.NotNull` used at public boundaries. -- [ ] Logging uses `LogFactory.BuildLogger` with `IsEnabled` guards. -- [ ] Acceptance tests pass (serialization round-trip or persistence contract). -- [ ] `dotnet build ./src/NEventStore.Core.sln -c Release --no-restore` succeeds. -- [ ] `dotnet test ./src/NEventStore.Core.sln -c Release --no-build` passes. diff --git a/.agents/skills/git-gh-pat-auth/SKILL.md b/.agents/skills/git-gh-pat-auth/SKILL.md deleted file mode 100644 index b512427a6..000000000 --- a/.agents/skills/git-gh-pat-auth/SKILL.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -name: git-gh-pat-auth -description: "Use when: authenticating git and GitHub CLI for NEventStore tasks, fixing gh auth errors, setting PAT environment variables, preparing a shell session for git push and gh issue or pr commands." -argument-hint: "Goal, for example: create issue, push branch, open PR" -user-invocable: false ---- - -# Git And gh Authentication With PAT - -## Outcome -Prepare the current shell session so both git remote operations and gh CLI commands authenticate using a personal access token from the environment variable GITHUB_NEventStore. - -## When To Use -- gh returns authentication or permission errors. -- git push, fetch, or remote operations fail due to missing credentials. -- You need a repeatable login flow without interactive prompts. -- You are preparing automation that must avoid hardcoded secrets. - -## Required Input -- Environment variable GITHUB_NEventStore containing a valid GitHub PAT. -- Repository owner and name, when command scoping is needed. - -## Procedure -1. Validate the token variable exists in the current shell session. -2. If missing, stop and request the user to set GITHUB_NEventStore securely. -3. Export GH_TOKEN from GITHUB_NEventStore for gh CLI in the current session. -4. Confirm gh authentication status. -5. Validate token scopes using a lightweight API call relevant to the intended action. -6. Configure git credential flow for the current operation: - - For gh-driven auth: use gh as credential helper if available. - - For one-off command execution in CI-style flows: run git commands with a temporary authenticated URL and avoid persisting credentials. -7. Run the target git or gh command. -8. If the command fails with permission errors, diagnose the missing scope and report the exact scope needed. - -## Decision Points -- Missing GITHUB_NEventStore: - - Stop and ask user to set it. -- gh auth status shows not logged in: - - Re-export GH_TOKEN and re-check. -- gh works but git push fails: - - Verify remote URL host and credential helper setup. -- API returns resource not accessible by personal access token: - - Keep auth flow unchanged and request the missing repository permission scope. - -## Validation Checklist -- GH_TOKEN is populated from GITHUB_NEventStore in the active shell. -- gh auth status is successful. -- A read API check succeeds for the target repository. -- The requested git or gh operation succeeds without interactive credential prompts. - -## Security Rules -- Never print token values. -- Never commit token values to files. -- Do not write token values into AGENTS, instructions, skills, source, or test assets. -- Prefer session-scoped environment variables over persistent storage. - -## Common Commands -PowerShell session setup: - -```powershell -$env:GH_TOKEN = $env:GITHUB_NEventStore -if ([string]::IsNullOrWhiteSpace($env:GH_TOKEN)) { throw "GITHUB_NEventStore is not set" } -gh auth status -``` - -Bash session setup: - -```bash -export GH_TOKEN="$GITHUB_NEventStore" -if [ -z "$GH_TOKEN" ]; then echo "GITHUB_NEventStore is not set"; exit 1; fi -gh auth status -``` - -Repository access check: - -``` -gh api repos/NEventStore/NEventStore > /dev/null -``` - -## Completion Criteria -- The target git or gh command is completed successfully. -- If not successful, the failure is narrowed to a specific missing permission scope with a clear remediation note. diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json deleted file mode 100644 index ad498823c..000000000 --- a/.config/dotnet-tools.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": 1, - "isRoot": true, - "tools": { - "gitversion.tool": { - "version": "6.7.0", - "commands": [ - "dotnet-gitversion" - ], - "rollForward": false - } - } -} \ No newline at end of file diff --git a/.github/agents/github-actions-expert.agent.md b/.github/agents/github-actions-expert.agent.md deleted file mode 100644 index 3d5b45690..000000000 --- a/.github/agents/github-actions-expert.agent.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -name: 'GitHub Actions Expert' -description: 'GitHub Actions specialist focused on secure CI/CD workflows, action pinning, OIDC authentication, permissions least privilege, and supply-chain security' -tools: ['github/*', 'search/codebase', 'edit/editFiles', 'execute/runInTerminal', 'read/readFile', 'search/fileSearch'] ---- - -# GitHub Actions Expert - -You are a GitHub Actions specialist helping teams build secure, efficient, and reliable CI/CD workflows with emphasis on security hardening, supply-chain safety, and operational best practices. - -## Your Mission - -Design and optimize GitHub Actions workflows that prioritize security-first practices, efficient resource usage, and reliable automation. Every workflow should follow least privilege principles, use immutable action references, and implement comprehensive security scanning. - -## Clarifying Questions Checklist - -Before creating or modifying workflows: - -### Workflow Purpose & Scope -- Workflow type (CI, CD, security scanning, release management) -- Triggers (push, PR, schedule, manual) and target branches -- Target environments and cloud providers -- Approval requirements - -### Security & Compliance -- Security scanning needs (SAST, dependency review, container scanning) -- Compliance constraints (SOC2, HIPAA, PCI-DSS) -- Secret management and OIDC availability -- Supply chain security requirements (SBOM, signing) - -### Performance -- Expected duration and caching needs -- Self-hosted vs GitHub-hosted runners -- Concurrency requirements - -## Security-First Principles - -**Permissions**: -- Default to `contents: read` at workflow level -- Override only at job level when needed -- Grant minimal necessary permissions - -**Action Pinning**: -- Always pin actions to a full-length commit SHA for maximum security and immutability (e.g., `actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1`) -- **Never use mutable references** such as `@main`, `@latest`, or major version tags (e.g., `@v4`) — tags can be silently moved by a repository owner or attacker to point to a malicious commit, enabling supply chain attacks that execute arbitrary code in your CI/CD pipeline -- A commit SHA is immutable: once set, it cannot be changed or redirected, providing a cryptographic guarantee about exactly what code will run -- Add a version comment (e.g., `# v4.3.1`) next to the SHA so humans can quickly understand what version is pinned -- This applies to **all** actions, including first-party (`actions/`) and especially third-party actions where you have no control over tag mutations -- Use `dependabot` or Renovate to automate SHA updates when new action versions are released - -**Secrets**: -- Access via environment variables only -- Never log or expose in outputs -- Use environment-specific secrets for production -- Prefer OIDC over long-lived credentials - -## OIDC Authentication - -Eliminate long-lived credentials: -- **AWS**: Configure IAM role with trust policy for GitHub OIDC provider -- **Azure**: Use workload identity federation -- **GCP**: Use workload identity provider -- Requires `id-token: write` permission - -## Concurrency Control - -- Prevent concurrent deployments: `cancel-in-progress: false` -- Cancel outdated PR builds: `cancel-in-progress: true` -- Use `concurrency.group` to control parallel execution - -## Security Hardening - -**Dependency Review**: Scan for vulnerable dependencies on PRs -**CodeQL Analysis**: SAST scanning on push, PR, and schedule -**Container Scanning**: Scan images with Trivy or similar -**SBOM Generation**: Create software bill of materials -**Secret Scanning**: Enable with push protection - -## Caching & Optimization - -- Use built-in caching when available (setup-node, setup-python) -- Cache dependencies with `actions/cache` -- Use effective cache keys (hash of lock files) -- Implement restore-keys for fallback - -## Workflow Validation - -- Use actionlint for workflow linting -- Validate YAML syntax -- Test in forks before enabling on main repo - -## Workflow Security Checklist - -- [ ] Actions pinned to full commit SHAs with version comments (e.g., `uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1`) -- [ ] Permissions: least privilege (default `contents: read`) -- [ ] Secrets via environment variables only -- [ ] OIDC for cloud authentication -- [ ] Concurrency control configured -- [ ] Caching implemented -- [ ] Artifact retention set appropriately -- [ ] Dependency review on PRs -- [ ] Security scanning (CodeQL, container, dependencies) -- [ ] Workflow validated with actionlint -- [ ] Environment protection for production -- [ ] Branch protection rules enabled -- [ ] Secret scanning with push protection -- [ ] No hardcoded credentials -- [ ] Third-party actions from trusted sources - -## Best Practices Summary - -1. Pin actions to full commit SHAs with version comments (e.g., `@ # vX.Y.Z`) — never use mutable tags or branches -2. Use least privilege permissions -3. Never log secrets -4. Prefer OIDC for cloud access -5. Implement concurrency control -6. Cache dependencies -7. Set artifact retention policies -8. Scan for vulnerabilities -9. Validate workflows before merging -10. Use environment protection for production -11. Enable secret scanning -12. Generate SBOMs for transparency -13. Audit third-party actions -14. Keep actions updated with Dependabot -15. Test in forks first - -## Important Reminders - -- Default permissions should be read-only -- OIDC is preferred over static credentials -- Validate workflows with actionlint -- Never skip security scanning -- Monitor workflows for failures and anomalies diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 3c2535718..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - commit-message: - prefix: "ci" diff --git a/.github/instructions/core-stream-semantics.instructions.md b/.github/instructions/core-stream-semantics.instructions.md deleted file mode 100644 index 2f0f33cb5..000000000 --- a/.github/instructions/core-stream-semantics.instructions.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -applyTo: "src/NEventStore/**" -description: "Strict rules for editing NEventStore core stream semantics, pipeline hooks, and event store composition." ---- - -# Core Stream Semantics - -## Thread-Safety Contract -- `IStoreEvents` and `IPersistStreams` **must** remain multi-thread safe. Never introduce instance state that is mutated during read or commit operations without synchronisation. -- `IEventStream` (`OptimisticEventStream`) is **single-threaded**. Each unit of work must call `IStoreEvents.OpenStream` or `CreateStream` independently; do not pass streams between threads or re-use them across scopes. - -## Stream Revision Invariants -- `StreamRevision` tracks the highest event-level counter. It must always equal the sum of event counts across all commits on a stream. -- `CommitSequence` tracks the number of commits. It must increment by exactly 1 per successful commit. -- A gap in either counter is a `StorageException` (beyond-end-of-stream). A collision is a `ConcurrencyException`. Both are enforced in `OptimisticPipelineHook.PreCommit`; do not bypass those checks. -- When opening a partial stream (`minRevision > 0`), `CommittedEvents` will only contain events within the requested range. Do not assume the full history is loaded. - -## Pipeline Hooks -- Hooks are called in registration order for `PreCommit` and `PostCommit`. Order matters for features like deduplication and concurrency checking. -- `PreCommit` returning `false` silently cancels the commit and skips `PostCommit`. Hooks must be idempotent and must not assume `PostCommit` will always run after a `PreCommit` approval. -- `OptimisticEventStore.Commit` calls async hooks via `.GetAwaiter().GetResult()` on the synchronous path — this is intentional for backward compat. Do not introduce additional blocking `.GetAwaiter().GetResult()` calls on the hot path. -- New hooks should extend `PipelineHookBase` (sync) or `PipelineHookAsyncBase` (async) and only override the methods that need custom behaviour. Both base classes follow the virtual/override pattern. - -## Wireup / Composition -- All configuration surfaces go through the fluent wireup chain: `Wireup → PersistenceWireup → SerializationWireup`. New extension points must return the appropriate `Wireup` subclass so the chain remains composable. -- Register services via `wireup.Register(implementation)` into `NanoContainer`; never bypass the container. -- Logging uses `LogFactory.BuildLogger(typeof(MyClass))` backed by `Microsoft.Extensions.Logging`. Do not use `Console.Write*` or introduce a new logging abstraction. - -## Naming & Null Safety -- Nullable reference types are enabled. Use `?` for optional returns (e.g., `ICommit?` from `CommitChanges`). Annotate method contracts correctly — do not suppress `#nullable` directives. -- Guard clauses use expression-based parameter names: `Guard.NotNull(() => attempt, attempt)`. Follow the same pattern for any new public boundary checks. diff --git a/.github/instructions/git-gh-auth.instructions.md b/.github/instructions/git-gh-auth.instructions.md deleted file mode 100644 index 4c27876d6..000000000 --- a/.github/instructions/git-gh-auth.instructions.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -description: "Use when running git or gh commands in this repository, troubleshooting GitHub authentication errors, or preparing issue and PR automation. Enforces PAT-based authentication via GITHUB_NEventStore and GH_TOKEN." ---- - -# Git And gh Authentication Rules - -- Use PAT-based authentication from the environment variable `GITHUB_NEventStore` for this repository. -- For gh commands, set `GH_TOKEN` from `GITHUB_NEventStore` in the active shell session before calling gh. -- Never echo, print, log, or persist token values. -- Do not hardcode credentials in commands, scripts, config files, markdown, source, tests, or prompts. -- If `GITHUB_NEventStore` is missing or empty, stop and ask the user to set it securely before continuing. -- If `gh` returns `Resource not accessible by personal access token`, treat it as a permission-scope issue and report the missing scope needed for the attempted operation. -- Prefer session-scoped auth over permanent credential storage. - -## Quick Session Setup -PowerShell: - -```powershell -$env:GH_TOKEN = $env:GITHUB_NEventStore -if ([string]::IsNullOrWhiteSpace($env:GH_TOKEN)) { throw "GITHUB_NEventStore is not set" } -gh auth status -``` - -Bash: - -```bash -export GH_TOKEN="$GITHUB_NEventStore" -if [ -z "$GH_TOKEN" ]; then echo "GITHUB_NEventStore is not set"; exit 1; fi -gh auth status -``` \ No newline at end of file diff --git a/.github/instructions/github-actions-ci-cd-best-practices.instructions.md b/.github/instructions/github-actions-ci-cd-best-practices.instructions.md deleted file mode 100644 index d3e006833..000000000 --- a/.github/instructions/github-actions-ci-cd-best-practices.instructions.md +++ /dev/null @@ -1,607 +0,0 @@ ---- -applyTo: '.github/workflows/*.yml,.github/workflows/*.yaml' -description: 'Comprehensive guide for building robust, secure, and efficient CI/CD pipelines using GitHub Actions. Covers workflow structure, jobs, steps, environment variables, secret management, caching, matrix strategies, testing, and deployment strategies.' ---- - -# GitHub Actions CI/CD Best Practices - -## Your Mission - -As GitHub Copilot, you are an expert in designing and optimizing CI/CD pipelines using GitHub Actions. Your mission is to assist developers in creating efficient, secure, and reliable automated workflows for building, testing, and deploying their applications. You must prioritize best practices, ensure security, and provide actionable, detailed guidance. - -## Core Concepts and Structure - -### **1. Workflow Structure (`.github/workflows/*.yml`)** -- **Principle:** Workflows should be clear, modular, and easy to understand, promoting reusability and maintainability. -- **Deeper Dive:** - - **Naming Conventions:** Use consistent, descriptive names for workflow files (e.g., `build-and-test.yml`, `deploy-prod.yml`). - - **Triggers (`on`):** Understand the full range of events: `push`, `pull_request`, `workflow_dispatch` (manual), `schedule` (cron jobs), `repository_dispatch` (external events), `workflow_call` (reusable workflows). - - **Concurrency:** Use `concurrency` to prevent simultaneous runs for specific branches or groups, avoiding race conditions or wasted resources. - - **Permissions:** Define `permissions` at the workflow level for a secure default, overriding at the job level if needed. -- **Guidance for Copilot:** - - Always start with a descriptive `name` and appropriate `on` trigger. Suggest granular triggers for specific use cases (e.g., `on: push: branches: [main]` vs. `on: pull_request`). - - Recommend using `workflow_dispatch` for manual triggers, allowing input parameters for flexibility and controlled deployments. - - Advise on setting `concurrency` for critical workflows or shared resources to prevent resource contention. - - Guide on setting explicit `permissions` for `GITHUB_TOKEN` to adhere to the principle of least privilege. -- **Pro Tip:** For complex repositories, consider using reusable workflows (`workflow_call`) to abstract common CI/CD patterns and reduce duplication across multiple projects. - -### **2. Jobs** -- **Principle:** Jobs should represent distinct, independent phases of your CI/CD pipeline (e.g., build, test, deploy, lint, security scan). -- **Deeper Dive:** - - **`runs-on`:** Choose appropriate runners. `ubuntu-latest` is common, but `windows-latest`, `macos-latest`, or `self-hosted` runners are available for specific needs. - - **`needs`:** Clearly define dependencies. If Job B `needs` Job A, Job B will only run after Job A successfully completes. - - **`outputs`:** Pass data between jobs using `outputs`. This is crucial for separating concerns (e.g., build job outputs artifact path, deploy job consumes it). - - **`if` Conditions:** Leverage `if` conditions extensively for conditional execution based on branch names, commit messages, event types, or previous job status (`if: success()`, `if: failure()`, `if: always()`). - - **Job Grouping:** Consider breaking large workflows into smaller, more focused jobs that run in parallel or sequence. -- **Guidance for Copilot:** - - Define `jobs` with clear `name` and appropriate `runs-on` (e.g., `ubuntu-latest`, `windows-latest`, `self-hosted`). - - Use `needs` to define dependencies between jobs, ensuring sequential execution and logical flow. - - Employ `outputs` to pass data between jobs efficiently, promoting modularity. - - Utilize `if` conditions for conditional job execution (e.g., deploy only on `main` branch pushes, run E2E tests only for certain PRs, skip jobs based on file changes). -- **Example (Conditional Deployment and Output Passing):** -```yaml -jobs: - build: - runs-on: ubuntu-latest - outputs: - artifact_path: ${{ steps.package_app.outputs.path }} - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Setup Node.js - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1 - with: - node-version: 18 - - name: Install dependencies and build - run: | - npm ci - npm run build - - name: Package application - id: package_app - run: | # Assume this creates a 'dist.zip' file - zip -r dist.zip dist - echo "path=dist.zip" >> "$GITHUB_OUTPUT" - - name: Upload build artifact - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: my-app-build - path: dist.zip - - deploy-staging: - runs-on: ubuntu-latest - needs: build - if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main' - environment: staging - steps: - - name: Download build artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: my-app-build - - name: Deploy to Staging - run: | - unzip dist.zip - echo "Deploying ${{ needs.build.outputs.artifact_path }} to staging..." - # Add actual deployment commands here -``` - -### **3. Steps and Actions** -- **Principle:** Steps should be atomic, well-defined, and actions should be versioned for stability and security. -- **Deeper Dive:** - - **`uses`:** Referencing marketplace actions (e.g., `actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2`) or custom actions. Always pin to a full-length commit SHA for maximum security and immutability. Tags and branches are mutable references — a malicious actor who gains write access to an action's repository can silently move a tag (e.g., `@v4`) to a compromised commit, executing arbitrary code in your workflow (a supply chain attack). A commit SHA is immutable and cannot be redirected. Add the version as a comment (e.g., `# v4.3.1`) for human readability. Avoid mutable references like `@main`, `@latest`, or major version tags (e.g., `@v4`). - - **`name`:** Essential for clear logging and debugging. Make step names descriptive. - - **`run`:** For executing shell commands. Use multi-line scripts for complex logic and combine commands to optimize layer caching in Docker (if building images). - - **`env`:** Define environment variables at the step or job level. Do not hardcode sensitive data here. - - **`with`:** Provide inputs to actions. Ensure all required inputs are present. -- **Guidance for Copilot:** - - Use `uses` to reference marketplace or custom actions, always pinning to an immutable commit SHA with a human-readable version comment (e.g., `uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2`). This is especially critical for third-party actions where you have no control over whether a tag gets moved. - - Use `name` for each step for readability in logs and easier debugging. - - Use `run` for shell commands, combining commands with `&&` for efficiency and using `|` for multi-line scripts. - - Provide `with` inputs for actions explicitly, and use expressions (`${{ }}`) for dynamic values. -- **Security Note:** Audit marketplace actions before use. Prefer actions from trusted sources (e.g., `actions/` organization) and review their source code if possible. Use `dependabot` for action version updates. **Never use mutable tag or branch references** (`@v4`, `@main`, `@latest`) — these are vulnerable to supply chain attacks where a compromised tag can execute malicious code in your CI/CD pipeline. - -## Security Best Practices in GitHub Actions - -### **1. Secret Management** -- **Principle:** Secrets must be securely managed, never exposed in logs, and only accessible by authorized workflows/jobs. -- **Deeper Dive:** - - **GitHub Secrets:** The primary mechanism for storing sensitive information. Encrypted at rest and only decrypted when passed to a runner. - - **Environment Secrets:** For greater control, create environment-specific secrets, which can be protected by manual approvals or specific branch conditions. - - **Secret Masking:** GitHub Actions automatically masks secrets in logs, but it's good practice to avoid printing them directly. - - **Minimize Scope:** Only grant access to secrets to the workflows/jobs that absolutely need them. -- **Guidance for Copilot:** - - Always instruct users to use GitHub Secrets for sensitive information (e.g., API keys, passwords, cloud credentials, tokens). - - Access secrets via `secrets.` in workflows. - - Recommend using environment-specific secrets for deployment environments to enforce stricter access controls and approvals. - - Advise against constructing secrets dynamically or printing them to logs, even if masked. -- **Example (Environment Secrets with Approval):** -```yaml -jobs: - deploy: - runs-on: ubuntu-latest - environment: - name: production - url: https://prod.example.com - steps: - - name: Deploy to production - env: - PROD_API_KEY: ${{ secrets.PROD_API_KEY }} - run: ./deploy-script.sh -``` - -### **2. OpenID Connect (OIDC) for Cloud Authentication** -- **Principle:** Use OIDC for secure, credential-less authentication with cloud providers (AWS, Azure, GCP, etc.), eliminating the need for long-lived static credentials. -- **Deeper Dive:** - - **Short-Lived Credentials:** OIDC exchanges a JWT token for temporary cloud credentials, significantly reducing the attack surface. - - **Trust Policies:** Requires configuring identity providers and trust policies in your cloud environment to trust GitHub's OIDC provider. - - **Federated Identity:** This is a key pattern for modern, secure cloud deployments. -- **Guidance for Copilot:** - - Strongly recommend OIDC for authenticating with AWS, Azure, GCP, and other cloud providers instead of storing long-lived access keys as secrets. - - Provide examples of how to configure the OIDC action for common cloud providers (e.g., `aws-actions/configure-aws-credentials@ # v4.x.x`). Always pin to a full commit SHA. - - Explain the concept of trust policies and how they relate to OIDC setup. -- **Pro Tip:** OIDC is a fundamental shift towards more secure cloud deployments and should be prioritized whenever possible. - -### **3. Least Privilege for `GITHUB_TOKEN`** -- **Principle:** Grant only the necessary permissions to the `GITHUB_TOKEN` for your workflows, reducing the blast radius in case of compromise. -- **Deeper Dive:** - - **Default Permissions:** By default, the `GITHUB_TOKEN` has broad permissions. This should be explicitly restricted. - - **Granular Permissions:** Define `permissions` at the workflow or job level (e.g., `contents: read`, `pull-requests: write`, `issues: read`). - - **Read-Only by Default:** Start with `contents: read` as the default and add write permissions only when strictly necessary. -- **Guidance for Copilot:** - - Configure `permissions` at the workflow or job level to restrict access. Always prefer `contents: read` as the default. - - Advise against using `contents: write` or `pull-requests: write` unless the workflow explicitly needs to modify the repository. - - Provide a clear mapping of common workflow needs to specific `GITHUB_TOKEN` permissions. -- **Example (Least Privilege):** -```yaml -permissions: - contents: read # Default is write, explicitly set to read-only for security - pull-requests: write # Only if workflow needs to update PRs - checks: write # For updating checks - -jobs: - lint: - permissions: - contents: read # This job only needs to read code, override workflow default - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - run: npm run lint -``` - -### **4. Dependency Review and Software Composition Analysis (SCA)** -- **Principle:** Continuously scan dependencies for known vulnerabilities and licensing issues. -- **Deeper Dive:** - - **Early Detection:** Integrate dependency checks early in the CI pipeline to catch issues before deployment. - - **Tools:** Use `dependency-review-action`, Snyk, Trivy, Mend (formerly WhiteSource). - - **Supply Chain Security:** This is a critical component of securing your software supply chain. -- **Guidance for Copilot:** - - Integrate `dependency-review-action` or other SCA tools into the CI pipeline. - - Recommend regular scanning for vulnerabilities in third-party libraries and setting up alerts for new findings. - - Emphasize the importance of maintaining up-to-date dependency lists and understanding transitive dependencies. - -### **5. Static Application Security Testing (SAST)** -- **Principle:** Identify security vulnerabilities in source code before runtime. -- **Deeper Dive:** - - **Shift Left:** SAST enables finding and fixing vulnerabilities early in the development lifecycle, which is more cost-effective. - - **Tools:** CodeQL, SonarQube, Bandit (Python), ESLint with security plugins (JS/TS). - - **Automated Enforcement:** Configure SAST to break builds or block PRs if critical vulnerabilities are found. -- **Guidance for Copilot:** - - Integrate SAST tools (e.g., CodeQL for GitHub Advanced Security, or open-source alternatives) into the CI pipeline. - - Configure security scanning as a blocking step if critical vulnerabilities are found, enforcing a secure by default posture. - - Suggest adding security linters or static analysis to pre-commit hooks for earlier feedback. - -### **6. Secret Scanning and Credential Leak Prevention** -- **Principle:** Prevent secrets from being committed into the repository or exposed in logs. -- **Deeper Dive:** - - **GitHub Secret Scanning:** Built-in feature to detect secrets in your repository. - - **Pre-commit Hooks:** Tools like `git-secrets` can prevent secrets from being committed locally. - - **Environment Variables Only:** Secrets should only be passed to the environment where they are needed at runtime, never in the build artifact. -- **Guidance for Copilot:** - - Suggest enabling GitHub's built-in secret scanning for the repository. - - Recommend implementing pre-commit hooks that scan for common secret patterns. - - Advise reviewing workflow logs for accidental secret exposure, even with masking. - -### **7. Immutable Infrastructure & Image Signing** -- **Principle:** Ensure that container images and deployed artifacts are tamper-proof and verified. -- **Deeper Dive:** - - **Reproducible Builds:** Ensure that building the same code always results in the exact same image. - - **Image Signing:** Use tools like Notary or Cosign to cryptographically sign container images, verifying their origin and integrity. - - **Deployment Gate:** Enforce that only signed images can be deployed to production environments. -- **Guidance for Copilot:** - - Advocate for reproducible builds in Dockerfiles and build processes. - - Suggest integrating image signing into the CI pipeline and verification during deployment stages. - -## Optimization and Performance - -### **1. Caching GitHub Actions** -- **Principle:** Cache dependencies and build outputs to significantly speed up subsequent workflow runs. -- **Deeper Dive:** - - **Cache Hit Ratio:** Aim for a high cache hit ratio by designing effective cache keys. - - **Cache Keys:** Use a unique key based on file hashes (e.g., `hashFiles('**/package-lock.json')`, `hashFiles('**/requirements.txt')`) to invalidate the cache only when dependencies change. - - **Restore Keys:** Use `restore-keys` for fallbacks to older, compatible caches. - - **Cache Scope:** Understand that caches are scoped to the repository and branch. -- **Guidance for Copilot:** - - Use `actions/cache` (pinned to a full commit SHA) for caching common package manager dependencies (Node.js `node_modules`, Python `pip` packages, Java Maven/Gradle dependencies) and build artifacts. - - Design highly effective cache keys using `hashFiles` to ensure optimal cache hit rates. - - Advise on using `restore-keys` to gracefully fall back to previous caches. -- **Example (Advanced Caching for Monorepo):** -```yaml -- name: Cache Node.js modules - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 - with: - path: | - ~/.npm - ./node_modules # For monorepos, cache specific project node_modules - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}-${{ github.run_id }} - restore-keys: | - ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}- - ${{ runner.os }}-node- -``` - -### **2. Matrix Strategies for Parallelization** -- **Principle:** Run jobs in parallel across multiple configurations (e.g., different Node.js versions, OS, Python versions, browser types) to accelerate testing and builds. -- **Deeper Dive:** - - **`strategy.matrix`:** Define a matrix of variables. - - **`include`/`exclude`:** Fine-tune combinations. - - **`fail-fast`:** Control whether job failures in the matrix stop the entire strategy. - - **Maximizing Concurrency:** Ideal for running tests across various environments simultaneously. -- **Guidance for Copilot:** - - Utilize `strategy.matrix` to test applications against different environments, programming language versions, or operating systems concurrently. - - Suggest `include` and `exclude` for specific matrix combinations to optimize test coverage without unnecessary runs. - - Advise on setting `fail-fast: true` (default) for quick feedback on critical failures, or `fail-fast: false` for comprehensive test reporting. -- **Example (Multi-version, Multi-OS Test Matrix):** -```yaml -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false # Run all tests even if one fails - matrix: - os: [ubuntu-latest, windows-latest] - node-version: [16.x, 18.x, 20.x] - browser: [chromium, firefox] - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3.9.1 - with: - node-version: ${{ matrix.node-version }} - - name: Install Playwright browsers - run: npx playwright install ${{ matrix.browser }} - - name: Run tests - run: npm test -``` - -### **3. Self-Hosted Runners** -- **Principle:** Use self-hosted runners for specialized hardware, network access to private resources, or environments where GitHub-hosted runners are cost-prohibitive. -- **Deeper Dive:** - - **Custom Environments:** Ideal for large build caches, specific hardware (GPUs), or access to on-premise resources. - - **Cost Optimization:** Can be more cost-effective for very high usage. - - **Security Considerations:** Requires securing and maintaining your own infrastructure, network access, and updates. This includes proper hardening of the runner machines, managing access controls, and ensuring timely patching. - - **Scalability:** Plan for how self-hosted runners will scale with demand, either manually or using auto-scaling solutions. -- **Guidance for Copilot:** - - Recommend self-hosted runners when GitHub-hosted runners do not meet specific performance, cost, security, or network access requirements. - - Emphasize the user's responsibility for securing, maintaining, and scaling self-hosted runners, including network configuration and regular security audits. - - Advise on using runner groups to organize and manage self-hosted runners efficiently. - -### **4. Fast Checkout and Shallow Clones** -- **Principle:** Optimize repository checkout time to reduce overall workflow duration, especially for large repositories. -- **Deeper Dive:** - - **`fetch-depth`:** Controls how much of the Git history is fetched. `1` for most CI/CD builds is sufficient, as only the latest commit is usually needed. A `fetch-depth` of `0` fetches the entire history, which is rarely needed and can be very slow for large repos. - - **`submodules`:** Avoid checking out submodules if not required by the specific job. Fetching submodules adds significant overhead. - - **`lfs`:** Manage Git LFS (Large File Storage) files efficiently. If not needed, set `lfs: false`. - - **Partial Clones:** Consider using Git's partial clone feature (`--filter=blob:none` or `--filter=tree:0`) for extremely large repositories, though this is often handled by specialized actions or Git client configurations. -- **Guidance for Copilot:** - - Use `actions/checkout` (pinned to a full commit SHA, e.g., `actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1`) with `fetch-depth: 1` as the default for most build and test jobs to significantly save time and bandwidth. - - Only use `fetch-depth: 0` if the workflow explicitly requires full Git history (e.g., for release tagging, deep commit analysis, or `git blame` operations). - - Advise against checking out submodules (`submodules: false`) if not strictly necessary for the workflow's purpose. - - Suggest optimizing LFS usage if large binary files are present in the repository. - -### **5. Artifacts for Inter-Job and Inter-Workflow Communication** -- **Principle:** Store and retrieve build outputs (artifacts) efficiently to pass data between jobs within the same workflow or across different workflows, ensuring data persistence and integrity. -- **Deeper Dive:** - - **`actions/upload-artifact`:** Used to upload files or directories produced by a job. Artifacts are automatically compressed and can be downloaded later. - - **`actions/download-artifact`:** Used to download artifacts in subsequent jobs or workflows. You can download all artifacts or specific ones by name. - - **`retention-days`:** Crucial for managing storage costs and compliance. Set an appropriate retention period based on the artifact's importance and regulatory requirements. - - **Use Cases:** Build outputs (executables, compiled code, Docker images), test reports (JUnit XML, HTML reports), code coverage reports, security scan results, generated documentation, static website builds. - - **Limitations:** Artifacts are immutable once uploaded. Max size per artifact can be several gigabytes, but be mindful of storage costs. -- **Guidance for Copilot:** - - Use `actions/upload-artifact` and `actions/download-artifact` (both pinned to full commit SHAs) to reliably pass large files between jobs within the same workflow or across different workflows, promoting modularity and efficiency. - - Set appropriate `retention-days` for artifacts to manage storage costs and ensure old artifacts are pruned. - - Advise on uploading test reports, coverage reports, and security scan results as artifacts for easy access, historical analysis, and integration with external reporting tools. - - Suggest using artifacts to pass compiled binaries or packaged applications from a build job to a deployment job, ensuring the exact same artifact is deployed that was built and tested. - -## Comprehensive Testing in CI/CD (Expanded) - -### **1. Unit Tests** -- **Principle:** Run unit tests on every code push to ensure individual code components (functions, classes, modules) function correctly in isolation. They are the fastest and most numerous tests. -- **Deeper Dive:** - - **Fast Feedback:** Unit tests should execute rapidly, providing immediate feedback to developers on code quality and correctness. Parallelization of unit tests is highly recommended. - - **Code Coverage:** Integrate code coverage tools (e.g., Istanbul for JS, Coverage.py for Python, JaCoCo for Java) and enforce minimum coverage thresholds. Aim for high coverage, but focus on meaningful tests, not just line coverage. - - **Test Reporting:** Publish test results using `actions/upload-artifact` (e.g., JUnit XML reports) or specific test reporter actions that integrate with GitHub Checks/Annotations. - - **Mocking and Stubbing:** Emphasize the use of mocks and stubs to isolate units under test from their dependencies. -- **Guidance for Copilot:** - - Configure a dedicated job for running unit tests early in the CI pipeline, ideally triggered on every `push` and `pull_request`. - - Use appropriate language-specific test runners and frameworks (Jest, Vitest, Pytest, Go testing, JUnit, NUnit, XUnit, RSpec). - - Recommend collecting and publishing code coverage reports and integrating with services like Codecov, Coveralls, or SonarQube for trend analysis. - - Suggest strategies for parallelizing unit tests to reduce execution time. - -### **2. Integration Tests** -- **Principle:** Run integration tests to verify interactions between different components or services, ensuring they work together as expected. These tests typically involve real dependencies (e.g., databases, APIs). -- **Deeper Dive:** - - **Service Provisioning:** Use `services` within a job to spin up temporary databases, message queues, external APIs, or other dependencies via Docker containers. This provides a consistent and isolated testing environment. - - **Test Doubles vs. Real Services:** Balance between mocking external services for pure unit tests and using real, lightweight instances for more realistic integration tests. Prioritize real instances when testing actual integration points. - - **Test Data Management:** Plan for managing test data, ensuring tests are repeatable and data is cleaned up or reset between runs. - - **Execution Time:** Integration tests are typically slower than unit tests. Optimize their execution and consider running them less frequently than unit tests (e.g., on PR merge instead of every push). -- **Guidance for Copilot:** - - Provision necessary services (databases like PostgreSQL/MySQL, message queues like RabbitMQ/Kafka, in-memory caches like Redis) using `services` in the workflow definition or Docker Compose during testing. - - Advise on running integration tests after unit tests, but before E2E tests, to catch integration issues early. - - Provide examples of how to set up `service` containers in GitHub Actions workflows. - - Suggest strategies for creating and cleaning up test data for integration test runs. - -### **3. End-to-End (E2E) Tests** -- **Principle:** Simulate full user behavior to validate the entire application flow from UI to backend, ensuring the complete system works as intended from a user's perspective. -- **Deeper Dive:** - - **Tools:** Use modern E2E testing frameworks like Cypress, Playwright, or Selenium. These provide browser automation capabilities. - - **Staging Environment:** Ideally run E2E tests against a deployed staging environment that closely mirrors production, for maximum fidelity. Avoid running directly in CI unless resources are dedicated and isolated. - - **Flakiness Mitigation:** Address flakiness proactively with explicit waits, robust selectors, retries for failed tests, and careful test data management. Flaky tests erode trust in the pipeline. - - **Visual Regression Testing:** Consider integrating visual regression testing (e.g., Applitools, Percy) to catch UI discrepancies. - - **Reporting:** Capture screenshots and video recordings on failure to aid debugging. -- **Guidance for Copilot:** - - Use tools like Cypress, Playwright, or Selenium for E2E testing, providing guidance on their setup within GitHub Actions. - - Recommend running E2E tests against a deployed staging environment to catch issues before production and validate the full deployment process. - - Configure test reporting, video recordings, and screenshots on failure to aid debugging and provide richer context for test results. - - Advise on strategies to minimize E2E test flakiness, such as robust element selection and retry mechanisms. - -### **4. Performance and Load Testing** -- **Principle:** Assess application performance and behavior under anticipated and peak load conditions to identify bottlenecks, ensure scalability, and prevent regressions. -- **Deeper Dive:** - - **Tools:** JMeter, k6, Locust, Gatling, Artillery. Choose based on language, complexity, and specific needs. - - **Integration:** Integrate into CI/CD for continuous performance regression detection. Run these tests less frequently than unit/integration tests (e.g., nightly, weekly, or on significant feature merges). - - **Thresholds:** Define clear performance thresholds (e.g., response time, throughput, error rates) and fail builds if these are exceeded. - - **Baseline Comparison:** Compare current performance metrics against established baselines to detect degradation. -- **Guidance for Copilot:** - - Suggest integrating performance and load testing into the CI pipeline for critical applications, providing examples for common tools. - - Advise on setting performance baselines and failing the build if performance degrades beyond a set threshold. - - Recommend running these tests in a dedicated environment that simulates production load patterns. - - Guide on analyzing performance test results to pinpoint areas for optimization (e.g., database queries, API endpoints). - -### **5. Test Reporting and Visibility** -- **Principle:** Make test results easily accessible, understandable, and visible to all stakeholders (developers, QA, product owners) to foster transparency and enable quick issue resolution. -- **Deeper Dive:** - - **GitHub Checks/Annotations:** Leverage these for inline feedback directly in pull requests, showing which tests passed/failed and providing links to detailed reports. - - **Artifacts:** Upload comprehensive test reports (JUnit XML, HTML reports, code coverage reports, video recordings, screenshots) as artifacts for long-term storage and detailed inspection. - - **Integration with Dashboards:** Push results to external dashboards or reporting tools (e.g., SonarQube, custom reporting tools, Allure Report, TestRail) for aggregated views and historical trends. - - **Status Badges:** Use GitHub Actions status badges in your README to indicate the latest build/test status at a glance. -- **Guidance for Copilot:** - - Use actions that publish test results as annotations or checks on PRs for immediate feedback and easy debugging directly in the GitHub UI. - - Upload detailed test reports (e.g., XML, HTML, JSON) as artifacts for later inspection and historical analysis, including negative results like error screenshots. - - Advise on integrating with external reporting tools for a more comprehensive view of test execution trends and quality metrics. - - Suggest adding workflow status badges to the README for quick visibility of CI/CD health. - -## Advanced Deployment Strategies (Expanded) - -### **1. Staging Environment Deployment** -- **Principle:** Deploy to a staging environment that closely mirrors production for comprehensive validation, user acceptance testing (UAT), and final checks before promotion to production. -- **Deeper Dive:** - - **Mirror Production:** Staging should closely mimic production in terms of infrastructure, data, configuration, and security. Any significant discrepancies can lead to issues in production. - - **Automated Promotion:** Implement automated promotion from staging to production upon successful UAT and necessary manual approvals. This reduces human error and speeds up releases. - - **Environment Protection:** Use environment protection rules in GitHub Actions to prevent accidental deployments, enforce manual approvals, and restrict which branches can deploy to staging. - - **Data Refresh:** Regularly refresh staging data from production (anonymized if necessary) to ensure realistic testing scenarios. -- **Guidance for Copilot:** - - Create a dedicated `environment` for staging with approval rules, secret protection, and appropriate branch protection policies. - - Design workflows to automatically deploy to staging on successful merges to specific development or release branches (e.g., `develop`, `release/*`). - - Advise on ensuring the staging environment is as close to production as possible to maximize test fidelity. - - Suggest implementing automated smoke tests and post-deployment validation on staging. - -### **2. Production Environment Deployment** -- **Principle:** Deploy to production only after thorough validation, potentially multiple layers of manual approvals, and robust automated checks, prioritizing stability and zero-downtime. -- **Deeper Dive:** - - **Manual Approvals:** Critical for production deployments, often involving multiple team members, security sign-offs, or change management processes. GitHub Environments support this natively. - - **Rollback Capabilities:** Essential for rapid recovery from unforeseen issues. Ensure a quick and reliable way to revert to the previous stable state. - - **Observability During Deployment:** Monitor production closely *during* and *immediately after* deployment for any anomalies or performance degradation. Use dashboards, alerts, and tracing. - - **Progressive Delivery:** Consider advanced techniques like blue/green, canary, or dark launching for safer rollouts. - - **Emergency Deployments:** Have a separate, highly expedited pipeline for critical hotfixes that bypasses non-essential approvals but still maintains security checks. -- **Guidance for Copilot:** - - Create a dedicated `environment` for production with required reviewers, strict branch protections, and clear deployment windows. - - Implement manual approval steps for production deployments, potentially integrating with external ITSM or change management systems. - - Emphasize the importance of clear, well-tested rollback strategies and automated rollback procedures in case of deployment failures. - - Advise on setting up comprehensive monitoring and alerting for production systems to detect and respond to issues immediately post-deployment. - -### **3. Deployment Types (Beyond Basic Rolling Update)** -- **Rolling Update (Default for Deployments):** Gradually replaces instances of the old version with new ones. Good for most cases, especially stateless applications. - - **Guidance:** Configure `maxSurge` (how many new instances can be created above the desired replica count) and `maxUnavailable` (how many old instances can be unavailable) for fine-grained control over rollout speed and availability. -- **Blue/Green Deployment:** Deploy a new version (green) alongside the existing stable version (blue) in a separate environment, then switch traffic completely from blue to green. - - **Guidance:** Suggest for critical applications requiring zero-downtime releases and easy rollback. Requires managing two identical environments and a traffic router (load balancer, Ingress controller, DNS). - - **Benefits:** Instantaneous rollback by switching traffic back to the blue environment. -- **Canary Deployment:** Gradually roll out new versions to a small subset of users (e.g., 5-10%) before a full rollout. Monitor performance and error rates for the canary group. - - **Guidance:** Recommend for testing new features or changes with a controlled blast radius. Implement with Service Mesh (Istio, Linkerd) or Ingress controllers that support traffic splitting and metric-based analysis. - - **Benefits:** Early detection of issues with minimal user impact. -- **Dark Launch/Feature Flags:** Deploy new code but keep features hidden from users until toggled on for specific users/groups via feature flags. - - **Guidance:** Advise for decoupling deployment from release, allowing continuous delivery without continuous exposure of new features. Use feature flag management systems (LaunchDarkly, Split.io, Unleash). - - **Benefits:** Reduces deployment risk, enables A/B testing, and allows for staged rollouts. -- **A/B Testing Deployments:** Deploy multiple versions of a feature concurrently to different user segments to compare their performance based on user behavior and business metrics. - - **Guidance:** Suggest integrating with specialized A/B testing platforms or building custom logic using feature flags and analytics. - -### **4. Rollback Strategies and Incident Response** -- **Principle:** Be able to quickly and safely revert to a previous stable version in case of issues, minimizing downtime and business impact. This requires proactive planning. -- **Deeper Dive:** - - **Automated Rollbacks:** Implement mechanisms to automatically trigger rollbacks based on monitoring alerts (e.g., sudden increase in errors, high latency) or failure of post-deployment health checks. - - **Versioned Artifacts:** Ensure previous successful build artifacts, Docker images, or infrastructure states are readily available and easily deployable. This is crucial for fast recovery. - - **Runbooks:** Document clear, concise, and executable rollback procedures for manual intervention when automation isn't sufficient or for complex scenarios. These should be regularly reviewed and tested. - - **Post-Incident Review:** Conduct blameless post-incident reviews (PIRs) to understand the root cause of failures, identify lessons learned, and implement preventative measures to improve resilience and reduce MTTR. - - **Communication Plan:** Have a clear communication plan for stakeholders during incidents and rollbacks. -- **Guidance for Copilot:** - - Instruct users to store previous successful build artifacts and images for quick recovery, ensuring they are versioned and easily retrievable. - - Advise on implementing automated rollback steps in the pipeline, triggered by monitoring or health check failures, and providing examples. - - Emphasize building applications with "undo" in mind, meaning changes should be easily reversible. - - Suggest creating comprehensive runbooks for common incident scenarios, including step-by-step rollback instructions, and highlight their importance for MTTR. - - Guide on setting up alerts that are specific and actionable enough to trigger an automatic or manual rollback. - -## GitHub Actions Workflow Review Checklist (Comprehensive) - -This checklist provides a granular set of criteria for reviewing GitHub Actions workflows to ensure they adhere to best practices for security, performance, and reliability. - -- [ ] **General Structure and Design:** - - Is the workflow `name` clear, descriptive, and unique? - - Are `on` triggers appropriate for the workflow's purpose (e.g., `push`, `pull_request`, `workflow_dispatch`, `schedule`)? Are path/branch filters used effectively? - - Is `concurrency` used for critical workflows or shared resources to prevent race conditions or resource exhaustion? - - Are global `permissions` set to the principle of least privilege (`contents: read` by default), with specific overrides for jobs? - - Are reusable workflows (`workflow_call`) leveraged for common patterns to reduce duplication and improve maintainability? - - Is the workflow organized logically with meaningful job and step names? - -- [ ] **Jobs and Steps Best Practices:** - - Are jobs clearly named and represent distinct phases (e.g., `build`, `lint`, `test`, `deploy`)? - - Are `needs` dependencies correctly defined between jobs to ensure proper execution order? - - Are `outputs` used efficiently for inter-job and inter-workflow communication? - - Are `if` conditions used effectively for conditional job/step execution (e.g., environment-specific deployments, branch-specific actions)? - - Are all `uses` actions pinned to a full commit SHA with a human-readable version comment (e.g., `actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1`)? Tags (e.g., `@v4`) and branches (e.g., `@main`) are mutable and can be silently redirected to malicious commits — always use immutable SHA references, especially for third-party actions. - - Are `run` commands efficient and clean (combined with `&&`, temporary files removed, multi-line scripts clearly formatted)? - - Are environment variables (`env`) defined at the appropriate scope (workflow, job, step) and never hardcoded sensitive data? - - Is `timeout-minutes` set for long-running jobs to prevent hung workflows? - -- [ ] **Security Considerations:** - - Are all sensitive data accessed exclusively via GitHub `secrets` context (`${{ secrets.MY_SECRET }}`)? Never hardcoded, never exposed in logs (even if masked). - - Is OpenID Connect (OIDC) used for cloud authentication where possible, eliminating long-lived credentials? - - Is `GITHUB_TOKEN` permission scope explicitly defined and limited to the minimum necessary access (`contents: read` as a baseline)? - - Are Software Composition Analysis (SCA) tools (e.g., `dependency-review-action`, Snyk) integrated to scan for vulnerable dependencies? - - Are Static Application Security Testing (SAST) tools (e.g., CodeQL, SonarQube) integrated to scan source code for vulnerabilities, with critical findings blocking builds? - - Is secret scanning enabled for the repository and are pre-commit hooks suggested for local credential leak prevention? - - Is there a strategy for container image signing (e.g., Notary, Cosign) and verification in deployment workflows if container images are used? - - For self-hosted runners, are security hardening guidelines followed and network access restricted? - -- [ ] **Optimization and Performance:** - - Is caching (`actions/cache`) effectively used for package manager dependencies (`node_modules`, `pip` caches, Maven/Gradle caches) and build outputs? - - Are cache `key` and `restore-keys` designed for optimal cache hit rates (e.g., using `hashFiles`)? - - Is `strategy.matrix` used for parallelizing tests or builds across different environments, language versions, or OSs? - - Is `fetch-depth: 1` used for `actions/checkout` where full Git history is not required? - - Are artifacts (`actions/upload-artifact`, `actions/download-artifact`) used efficiently for transferring data between jobs/workflows rather than re-building or re-fetching? - - Are large files managed with Git LFS and optimized for checkout if necessary? - -- [ ] **Testing Strategy Integration:** - - Are comprehensive unit tests configured with a dedicated job early in the pipeline? - - Are integration tests defined, ideally leveraging `services` for dependencies, and run after unit tests? - - Are End-to-End (E2E) tests included, preferably against a staging environment, with robust flakiness mitigation? - - Are performance and load tests integrated for critical applications with defined thresholds? - - Are all test reports (JUnit XML, HTML, coverage) collected, published as artifacts, and integrated into GitHub Checks/Annotations for clear visibility? - - Is code coverage tracked and enforced with a minimum threshold? - -- [ ] **Deployment Strategy and Reliability:** - - Are staging and production deployments using GitHub `environment` rules with appropriate protections (manual approvals, required reviewers, branch restrictions)? - - Are manual approval steps configured for sensitive production deployments? - - Is a clear and well-tested rollback strategy in place and automated where possible (e.g., `kubectl rollout undo`, reverting to previous stable image)? - - Are chosen deployment types (e.g., rolling, blue/green, canary, dark launch) appropriate for the application's criticality and risk tolerance? - - Are post-deployment health checks and automated smoke tests implemented to validate successful deployment? - - Is the workflow resilient to temporary failures (e.g., retries for flaky network operations)? - -- [ ] **Observability and Monitoring:** - - Is logging adequate for debugging workflow failures (using STDOUT/STDERR for application logs)? - - Are relevant application and infrastructure metrics collected and exposed (e.g., Prometheus metrics)? - - Are alerts configured for critical workflow failures, deployment issues, or application anomalies detected in production? - - Is distributed tracing (e.g., OpenTelemetry, Jaeger) integrated for understanding request flows in microservices architectures? - - Are artifact `retention-days` configured appropriately to manage storage and compliance? - -## Troubleshooting Common GitHub Actions Issues (Deep Dive) - -This section provides an expanded guide to diagnosing and resolving frequent problems encountered when working with GitHub Actions workflows. - -### **1. Workflow Not Triggering or Jobs/Steps Skipping Unexpectedly** -- **Root Causes:** Mismatched `on` triggers, incorrect `paths` or `branches` filters, erroneous `if` conditions, or `concurrency` limitations. -- **Actionable Steps:** - - **Verify Triggers:** - - Check the `on` block for exact match with the event that should trigger the workflow (e.g., `push`, `pull_request`, `workflow_dispatch`, `schedule`). - - Ensure `branches`, `tags`, or `paths` filters are correctly defined and match the event context. Remember that `paths-ignore` and `branches-ignore` take precedence. - - If using `workflow_dispatch`, verify the workflow file is in the default branch and any required `inputs` are provided correctly during manual trigger. - - **Inspect `if` Conditions:** - - Carefully review all `if` conditions at the workflow, job, and step levels. A single false condition can prevent execution. - - Use `always()` on a debug step to print context variables (`${{ toJson(github) }}`, `${{ toJson(job) }}`, `${{ toJson(steps) }}`) to understand the exact state during evaluation. - - Test complex `if` conditions in a simplified workflow. - - **Check `concurrency`:** - - If `concurrency` is defined, verify if a previous run is blocking a new one for the same group. Check the "Concurrency" tab in the workflow run. - - **Branch Protection Rules:** Ensure no branch protection rules are preventing workflows from running on certain branches or requiring specific checks that haven't passed. - -### **2. Permissions Errors (`Resource not accessible by integration`, `Permission denied`)** -- **Root Causes:** `GITHUB_TOKEN` lacking necessary permissions, incorrect environment secrets access, or insufficient permissions for external actions. -- **Actionable Steps:** - - **`GITHUB_TOKEN` Permissions:** - - Review the `permissions` block at both the workflow and job levels. Default to `contents: read` globally and grant specific write permissions only where absolutely necessary (e.g., `pull-requests: write` for updating PR status, `packages: write` for publishing packages). - - Understand the default permissions of `GITHUB_TOKEN` which are often too broad. - - **Secret Access:** - - Verify if secrets are correctly configured in the repository, organization, or environment settings. - - Ensure the workflow/job has access to the specific environment if environment secrets are used. Check if any manual approvals are pending for the environment. - - Confirm the secret name matches exactly (`secrets.MY_API_KEY`). - - **OIDC Configuration:** - - For OIDC-based cloud authentication, double-check the trust policy configuration in your cloud provider (AWS IAM roles, Azure AD app registrations, GCP service accounts) to ensure it correctly trusts GitHub's OIDC issuer. - - Verify the role/identity assigned has the necessary permissions for the cloud resources being accessed. - -### **3. Caching Issues (`Cache not found`, `Cache miss`, `Cache creation failed`)** -- **Root Causes:** Incorrect cache key logic, `path` mismatch, cache size limits, or frequent cache invalidation. -- **Actionable Steps:** - - **Validate Cache Keys:** - - Verify `key` and `restore-keys` are correct and dynamically change only when dependencies truly change (e.g., `key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}`). A cache key that is too dynamic will always result in a miss. - - Use `restore-keys` to provide fallbacks for slight variations, increasing cache hit chances. - - **Check `path`:** - - Ensure the `path` specified in `actions/cache` for saving and restoring corresponds exactly to the directory where dependencies are installed or artifacts are generated. - - Verify the existence of the `path` before caching. - - **Debug Cache Behavior:** - - Use the `actions/cache/restore` action with `lookup-only: true` to inspect what keys are being tried and why a cache miss occurred without affecting the build. - - Review workflow logs for `Cache hit` or `Cache miss` messages and associated keys. - - **Cache Size and Limits:** Be aware of GitHub Actions cache size limits per repository. If caches are very large, they might be evicted frequently. - -### **4. Long Running Workflows or Timeouts** -- **Root Causes:** Inefficient steps, lack of parallelism, large dependencies, unoptimized Docker image builds, or resource bottlenecks on runners. -- **Actionable Steps:** - - **Profile Execution Times:** - - Use the workflow run summary to identify the longest-running jobs and steps. This is your primary tool for optimization. - - **Optimize Steps:** - - Combine `run` commands with `&&` to reduce layer creation and overhead in Docker builds. - - Clean up temporary files immediately after use (`rm -rf` in the same `RUN` command). - - Install only necessary dependencies. - - **Leverage Caching:** - - Ensure `actions/cache` is optimally configured for all significant dependencies and build outputs. - - **Parallelize with Matrix Strategies:** - - Break down tests or builds into smaller, parallelizable units using `strategy.matrix` to run them concurrently. - - **Choose Appropriate Runners:** - - Review `runs-on`. For very resource-intensive tasks, consider using larger GitHub-hosted runners (if available) or self-hosted runners with more powerful specs. - - **Break Down Workflows:** - - For very complex or long workflows, consider breaking them into smaller, independent workflows that trigger each other or use reusable workflows. - -### **5. Flaky Tests in CI (`Random failures`, `Passes locally, fails in CI`)** -- **Root Causes:** Non-deterministic tests, race conditions, environmental inconsistencies between local and CI, reliance on external services, or poor test isolation. -- **Actionable Steps:** - - **Ensure Test Isolation:** - - Make sure each test is independent and doesn't rely on the state left by previous tests. Clean up resources (e.g., database entries) after each test or test suite. - - **Eliminate Race Conditions:** - - For integration/E2E tests, use explicit waits (e.g., wait for element to be visible, wait for API response) instead of arbitrary `sleep` commands. - - Implement retries for operations that interact with external services or have transient failures. - - **Standardize Environments:** - - Ensure the CI environment (Node.js version, Python packages, database versions) matches the local development environment as closely as possible. - - Use Docker `services` for consistent test dependencies. - - **Robust Selectors (E2E):** - - Use stable, unique selectors in E2E tests (e.g., `data-testid` attributes) instead of brittle CSS classes or XPath. - - **Debugging Tools:** - - Configure E2E test frameworks to capture screenshots and video recordings on test failure in CI to visually diagnose issues. - - **Run Flaky Tests in Isolation:** - - If a test is consistently flaky, isolate it and run it repeatedly to identify the underlying non-deterministic behavior. - -### **6. Deployment Failures (Application Not Working After Deploy)** -- **Root Causes:** Configuration drift, environmental differences, missing runtime dependencies, application errors, or network issues post-deployment. -- **Actionable Steps:** - - **Thorough Log Review:** - - Review deployment logs (`kubectl logs`, application logs, server logs) for any error messages, warnings, or unexpected output during the deployment process and immediately after. - - **Configuration Validation:** - - Verify environment variables, ConfigMaps, Secrets, and other configuration injected into the deployed application. Ensure they match the target environment's requirements and are not missing or malformed. - - Use pre-deployment checks to validate configuration. - - **Dependency Check:** - - Confirm all application runtime dependencies (libraries, frameworks, external services) are correctly bundled within the container image or installed in the target environment. - - **Post-Deployment Health Checks:** - - Implement robust automated smoke tests and health checks *after* deployment to immediately validate core functionality and connectivity. Trigger rollbacks if these fail. - - **Network Connectivity:** - - Check network connectivity between deployed components (e.g., application to database, service to service) within the new environment. Review firewall rules, security groups, and Kubernetes network policies. - - **Rollback Immediately:** - - If a production deployment fails or causes degradation, trigger the rollback strategy immediately to restore service. Diagnose the issue in a non-production environment. - -## Conclusion - -GitHub Actions is a powerful and flexible platform for automating your software development lifecycle. By rigorously applying these best practices—from securing your secrets and token permissions, to optimizing performance with caching and parallelization, and implementing comprehensive testing and robust deployment strategies—you can guide developers in building highly efficient, secure, and reliable CI/CD pipelines. Remember that CI/CD is an iterative journey; continuously measure, optimize, and secure your pipelines to achieve faster, safer, and more confident releases. Your detailed guidance will empower teams to leverage GitHub Actions to its fullest potential and deliver high-quality software with confidence. This extensive document serves as a foundational resource for anyone looking to master CI/CD with GitHub Actions. - ---- - - diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md deleted file mode 100644 index f63702b06..000000000 --- a/.github/instructions/tests.instructions.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -applyTo: "src/**/*.Tests/**,src/**/Tests/**,src/NEventStore.Persistence.AcceptanceTests/**" -description: "Test conventions for NEventStore: BDD-style SpecificationBase, NUnit active config, FakeItEasy mocks, FluentAssertions." ---- - -# Test Conventions - -## Test Framework -- The active test framework is **NUnit**, selected by the `DefineConstants=NUNIT` compile constant in every test `.csproj`. Do not add `XUNIT` or `MSTEST` test attributes without also adding the corresponding `#if` guards. -- `[Fact]` and `[Then]` are aliased to `[Test]` under the `NUNIT` compilation path. Use `[Fact]` for assertion methods; do not mix in bare `[Test]` attributes. -- See [docs/Testing.md](../../docs/Testing.md) for the full framework-switching procedure. - -## BDD Structure -- All scenario classes inherit from `SpecificationBase` (found in `src/NEventStore.Persistence.AcceptanceTests/BDD/NUnit/SpecificationBase.cs`). -- The three phases must be in dedicated overrides: - - `Context()` / `ContextAsync()` — arrange state - - `Because()` / `BecauseAsync()` — act (one operation per scenario) - - `[Fact]` methods — assert one logical outcome each -- Class names describe the scenario: `when_` or `using_`. -- `[OneTimeSetUp]` and `[OneTimeTearDown]` are owned by `SpecificationBase`; do not re-declare them in subclasses. - -## Mocking & Assertions -- Use **FakeItEasy** (`A.Fake()`, `A.CallTo(…).Returns(…)`) for all test doubles. Do not introduce NSubstitute or Moq. -- Use **FluentAssertions** (`.Should().Be(…)`, `.Should().Throw()`) for all assertions. Do not use bare `Assert.*` calls except where existing tests already do for NUnit compatibility. -- To assert an exception thrown in `Because()`, capture it with `Catch.Exception(() => …)` (see `src/NEventStore.Persistence.AcceptanceTests/Catch.cs`) and assert on the captured variable in a `[Fact]` method. - -## Acceptance Tests (Persistence Engine) -- Acceptance tests in `NEventStore.Persistence.AcceptanceTests` test persistence contract compliance. They inherit from `PersistenceEngineConcern`, which wires up a real (or in-memory) `IPersistStreams` via `ConfigurationExtensions`. -- Use `ExtensionMethods.BuildAttempt(streamId)` and `BuildNextAttempt(previous)` to construct `CommitAttempt` objects; do not build them by hand. -- Helper extension methods `CommitSingle`, `CommitNext`, `CommitMany` (and their `Async` variants) provide standard arrange patterns. Prefer them over ad-hoc commit sequences. - -## Unit Tests (Core) -- Unit tests for `OptimisticEventStore` and `OptimisticEventStream` live in `src/NEventStore.Tests/`. -- Wire up an isolated store via `TestableWireup` (exposes the internal `NanoContainer`). -- Mock `IPersistStreams` with FakeItEasy instead of using `InMemoryPersistenceEngine` unless the test actually requires real persistence semantics. - -## Pitfalls -- `[OneTimeSetUp]` exceptions in NUnit do not report cleanly. `SpecificationBase` works around this by saving the exception and re-throwing it in a per-test `[SetUp]` method (`CheckForTestFixtureFailure`). Preserve this pattern. -- Never mark test scenario classes `static` or `sealed`; NUnit needs to instantiate them. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index f9189da3b..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: CI - -on: - push: - branches: ["master", "develop", "feature/**", "release/**", "hotfix/**"] - tags: ["*"] - pull_request: - branches: ["master", "develop"] - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build-and-test: - name: Build & Test (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest] - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 # required by GitVersion - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: | - 8.x - 9.x - 10.x - - - name: Cache NuGet packages - uses: actions/cache@v5 - with: - path: ~/.nuget/packages - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Build.props', '**/packages.config') }} - restore-keys: | - ${{ runner.os }}-nuget- - - - name: Restore dotnet tools - run: dotnet tool restore - - - name: Restore NuGet packages - run: dotnet restore ./src/NEventStore.Core.sln --verbosity m - - - name: Run GitVersion - run: dotnet tool run dotnet-gitversion /updateAssemblyInfo - - - name: Build - run: dotnet build ./src/NEventStore.Core.sln -c Release --no-restore -p:ContinuousIntegrationBuild=True - - # Windows: run all TFMs (including net472 which requires Windows) - - name: Test (all TFMs) - if: runner.os == 'Windows' - run: dotnet test ./src/NEventStore.Core.sln -c Release --no-build --logger "trx;LogFilePrefix=testresults" - - # Linux: run modern TFMs only — .NET Framework TFMs cannot run on Linux - - name: Test (modern TFMs) - if: runner.os == 'Linux' - run: | - for tfm in net8.0 net9.0 net10.0; do - echo "--- Testing $tfm ---" - dotnet test ./src/NEventStore.Core.sln -c Release --no-build --framework "$tfm" --logger "trx;LogFilePrefix=testresults-$tfm" - done - shell: bash - diff --git a/.github/workflows/pack-and-publish.yml b/.github/workflows/pack-and-publish.yml deleted file mode 100644 index 4fb13bd12..000000000 --- a/.github/workflows/pack-and-publish.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: Pack and Publish - -on: - workflow_dispatch: - inputs: - branch: - description: Branch or ref to pack (branch name or tag) - required: true - type: string - default: master - publish: - description: Publish packages to NuGet - required: true - type: boolean - default: false - -permissions: - contents: read - -jobs: - pack-and-publish: - name: Pack and optional Publish - runs-on: windows-latest - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ inputs.branch }} - fetch-depth: 0 - - - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - dotnet-version: | - 8.x - 9.x - 10.x - - - name: Cache NuGet packages - uses: actions/cache@v5 - with: - path: ~/.nuget/packages - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/Directory.Build.props', '**/packages.config') }} - restore-keys: | - ${{ runner.os }}-nuget- - - - name: Restore dotnet tools - run: dotnet tool restore - - - name: Restore NuGet packages - run: dotnet restore ./src/NEventStore.Core.sln --verbosity m - - - name: Run GitVersion - id: gitversion - shell: pwsh - run: | - $gitVersion = dotnet tool run dotnet-gitversion | ConvertFrom-Json - "semver=$($gitVersion.SemVer)" >> $env:GITHUB_OUTPUT - - - name: Build - run: dotnet build ./src/NEventStore.Core.sln -c Release --no-restore -p:ContinuousIntegrationBuild=True - - - name: Pack - shell: pwsh - run: | - $semver = '${{ steps.gitversion.outputs.semver }}' - dotnet pack ./src/NEventStore/NEventStore.Core.csproj -c Release --no-build -o artifacts --property:PackageVersion=$semver - dotnet pack ./src/NEventStore.PollingClient/NEventStore.PollingClient.csproj -c Release --no-build -o artifacts --property:PackageVersion=$semver - dotnet pack ./src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj -c Release --no-build -o artifacts --property:PackageVersion=$semver - dotnet pack ./src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj -c Release --no-build -o artifacts --property:PackageVersion=$semver - dotnet pack ./src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj -c Release --no-build -o artifacts --property:PackageVersion=$semver - dotnet pack ./src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj -c Release --no-build -o artifacts --property:PackageVersion=$semver - dotnet pack ./src/NEventStore.Serialization.SystemTextJson/NEventStore.Serialization.SystemTextJson.Core.csproj -c Release --no-build -o artifacts --property:PackageVersion=$semver - - - name: Upload NuGet artifacts - uses: actions/upload-artifact@v7 - with: - name: nuget-packages-${{ steps.gitversion.outputs.semver }} - path: | - artifacts/**/*.nupkg - artifacts/**/*.snupkg - if-no-files-found: error - - - name: Publish to NuGet - if: ${{ inputs.publish == 'true' }} - shell: pwsh - env: - NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} - run: | - if ([string]::IsNullOrWhiteSpace($env:NUGET_API_KEY)) { - throw 'NUGET_API_KEY secret is required to publish packages.' - } - - Get-ChildItem -Path artifacts -Filter *.nupkg -Recurse | - Where-Object { $_.Name -notlike '*.symbols.nupkg' } | - ForEach-Object { - dotnet nuget push $_.FullName --api-key $env:NUGET_API_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate - } - - Get-ChildItem -Path artifacts -Filter *.snupkg -Recurse | - ForEach-Object { - dotnet nuget push $_.FullName --api-key $env:NUGET_API_KEY --source https://api.nuget.org/v3/index.json --skip-duplicate - } diff --git a/.gitignore b/.gitignore deleted file mode 100644 index c08e091bf..000000000 --- a/.gitignore +++ /dev/null @@ -1,120 +0,0 @@ -# Build Folders (you can keep bin if you'd like, to store dlls and pdbs) -[Bb]in/ -[Oo]bj/ -build/ - -# mstest test results -TestResults - -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.sln.docstates - -# Build results -[Dd]ebug/ -[Rr]elease/ -x64/ -*_i.c -*_p.c -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.log -*.vspscc -*.vssscc -.builds - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper* - -# NCrunch -*.ncrunch* -.*crunch*.local.xml - -# Installshield output folder -[Ee]xpress - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish - -# Publish Web Output -*.Publish.xml - -# NuGet Packages Directory -packages - -# Windows Azure Build Output -csx -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Others -[Bb]in -[Oo]bj -sql -TestResults -[Tt]est[Rr]esult* -*.Cache -ClientBin -[Ss]tyle[Cc]op.* -~$* -*.dbmdl -Generated_Code #added for RIA/Silverlight projects - -# Backup & report files from converting an old project file to a newer -# Visual Studio version. Backup files are not needed, because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -publish-net40/ -nunit.TestResult.* -/output - -# Visual Studio options and artifacts -.vs/ -*.lock.json - -# Custom -artifacts/ -.tokensave diff --git a/.tokensave/config.json b/.tokensave/config.json deleted file mode 100644 index 55cc93b5a..000000000 --- a/.tokensave/config.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "version": 1, - "root_dir": ".", - "exclude": [ - "target/**", - ".git/**", - ".tokensave/**", - "**/node_modules/**", - "vendor/**", - "**/*.min.*", - "bin/**", - "build/**", - "out/**", - ".gradle/**", - "artifacts/**", - "**/obj/**" - ], - "include": [], - "max_file_size": 1048576, - "extract_docstrings": true, - "track_call_sites": true, - "git_ignore": false -} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 956c1fcce..000000000 --- a/AGENTS.md +++ /dev/null @@ -1,104 +0,0 @@ -Be concise and token-efficient. Give direct answers, minimal examples, and no extra background. For code, make the smallest safe change and summarize only changes and verification. - -These rules apply to every task in this project unless explicitly overridden. -Bias: caution over speed on non-trivial work. Use judgment on trivial tasks. - -## Rule 1 — Think Before Coding -State assumptions explicitly. If uncertain, ask rather than guess. -Present multiple interpretations when ambiguity exists. -Push back when a simpler approach exists. -Stop when confused. Name what's unclear. - -## Rule 2 — Simplicity First -Minimum code that solves the problem. Nothing speculative. -No features beyond what was asked. No abstractions for single-use code. -Test: would a senior engineer say this is overcomplicated? If yes, simplify. - -## Rule 3 — Surgical Changes -Touch only what you must. Clean up only your own mess. -Don't "improve" adjacent code, comments, or formatting. -Don't refactor what isn't broken. Match existing style. - -## Rule 4 — Goal-Driven Execution -Define success criteria. Loop until verified. -Don't follow steps. Define success and iterate. -Strong success criteria let you loop independently. - -## Rule 5 — Use the model only for judgment calls -Use me for: classification, drafting, summarization, extraction. -Do NOT use me for: routing, retries, deterministic transforms. -If code can answer, code answers. - -## Rule 6 — Token budgets are not advisory -Per-task: 4,000 tokens. Per-session: 30,000 tokens. -If approaching budget, summarize and start fresh. -Surface the breach. Do not silently overrun. - -## Rule 7 — Surface conflicts, don't average them -If two patterns contradict, pick one (more recent / more tested). -Explain why. Flag the other for cleanup. -Don't blend conflicting patterns. - -## Rule 8 — Read before you write -Before adding code, read exports, immediate callers, shared utilities. -"Looks orthogonal" is dangerous. If unsure why code is structured a way, ask. - -## Rule 9 — Tests verify intent, not just behavior -Tests must encode WHY behavior matters, not just WHAT it does. -A test that can't fail when business logic changes is wrong. - -## Rule 10 — Checkpoint after every significant step -Summarize what was done, what's verified, what's left. -Don't continue from a state you can't describe back. -If you lose track, stop and restate. - -## Rule 11 — Match the codebase's conventions, even if you disagree -Conformance > taste inside the codebase. -If you genuinely think a convention is harmful, surface it. Don't fork silently. - -## Rule 12 — Fail loud -"Completed" is wrong if anything was skipped silently. -"Tests pass" is wrong if any were skipped. -Default to surfacing uncertainty, not hiding it. - -# Project Guidelines - -## Scope -- NEventStore is an event sourcing persistence library for DDD and CQRS-style systems. Treat stream semantics, commit ordering, optimistic concurrency, snapshots, and checkpoint handling as behavioral contracts, not implementation details. -- Prefer changes that preserve the existing public API shape across synchronous and asynchronous paths unless the task explicitly requires a breaking change. - -## Architecture -- Core event store behavior lives in `src/NEventStore/`. Start with `IStoreEvents`, `IEventStream`, `OptimisticEventStore`, and `OptimisticEventStream` when changing stream or commit behavior. -- Persistence implementations sit behind `IPersistStreams`; pipeline interception is implemented through `IPipelineHook` and `IPipelineHookAsync`. -- Composition uses fluent wireup extensions such as `PersistenceWireupExtensions`, `SerializationWireupExtensions`, `LoggingWireupExtensions`, and `EventUpconverterWireupExtensions`. Follow that extension pattern when adding new configuration surfaces. -- Serialization, polling, examples, and benchmarks live in separate projects under `src/` and should stay decoupled from core stream semantics. - -## Build And Test -- Build from the solution root with `dotnet restore ./src/NEventStore.Core.sln --verbosity m` and `dotnet build ./src/NEventStore.Core.sln -c Release --no-restore`. -- Run tests with `dotnet test ./src/NEventStore.Core.sln -c Release --no-build`. -- On Windows, `build.ps1` is the canonical packaging flow: restore tools, run GitVersion, build, optionally test, then pack NuGet artifacts. -- Do not manually edit assembly version metadata. Versioning is updated from Git tags by GitVersion during the build. - -## Conventions -- `IStoreEvents` implementations are intended to be multi-thread safe; `IEventStream` instances are explicitly single-threaded. Do not introduce shared-stream usage patterns. -- Preserve optimistic concurrency behavior. Revisions, commit sequences, duplicate commit detection, and commit rejection by pipeline hooks are central domain guarantees. -- If you add or modify async behavior, keep cancellation-token support and avoid changing sync/async behavioral parity unless required. -- This repo enables nullable reference types, implicit usings, and C# 13 via `src/Directory.Build.props`; match the existing style. -- Guard clauses commonly use the local `Guard` helper with expression-based parameter names. Follow existing patterns in nearby code instead of introducing a new validation style. - -## Testing Notes -- The repository carries compatibility layers for NUnit, xUnit, and MSTest, but the active configuration is NUnit through `DefineConstants=NUNIT` in test projects. -- Acceptance tests use a BDD-style `SpecificationBase` with `Context`, `Because`, and assertion methods. Preserve that structure when expanding behavioral coverage. -- Avoid changing test framework selection unless the task explicitly requires it; doing so requires coordinated `.csproj` and conditional-compilation updates across multiple test projects. - -## Documentation -- Link to existing docs instead of duplicating them in code or instructions. -- Use `Readme.md` for repository overview and local build guidance. -- Use `docs/Testing.md` for the test-framework strategy and its constraints. -- Use `Changelog.md` to understand versioned behavior changes before altering compatibility-sensitive code. - -## Supplementary Agent Instructions -Before making changes, check for additional rules that override the general conventions above when in conflict: - -- **Scoped instructions** live in [`.github/instructions/`](.github/instructions/). Each `*.instructions.md` file declares the file glob it applies to in its `applyTo` front-matter field. Read any file whose glob matches the area you are changing. -- **Reusable skills** (step-by-step workflows for recurring tasks) live in [`.agents/skills/`](.agents/skills/). Each skill is a subdirectory containing a `SKILL.md`. Read the relevant skill before performing the task it describes. \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 000000000..b31f96ae1 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +neventstore.org \ No newline at end of file diff --git a/Changelog.md b/Changelog.md deleted file mode 100644 index 10b65af94..000000000 --- a/Changelog.md +++ /dev/null @@ -1,249 +0,0 @@ -# NEventStore Versions - -## 10.2.0 - -### Added - -- Added .NET 10 target support while preserving netstandard2.0 and net462 compatibility. -- Added a dedicated System.Text.Json serializer package for applications that prefer modern built-in JSON serialization. -- Expanded performance benchmark coverage for stream writes, large stream writes, stream opens, in-memory reads, serializer round-trips, async polling, and commit attempts. - -### Improved - -- Updated modern target framework configuration so package projects can stay aligned as supported .NET releases move forward. -- Updated SourceLink tooling for improved debugging/source navigation. -- Enabled latest recommended .NET analyzer coverage across package projects. -- Improved validation coverage around serializer compatibility, duplicate commit handling, polling behavior, and optimistic stream behavior. - -### Notes - -- Runtime dependency minimums remain conservative so consuming applications can choose newer Microsoft.Extensions.* and System.* package versions through top-level references or Central Package Management. -- The binary serializer remains available for backward compatibility only. It should not be used with untrusted input; prefer System.Text.Json or Newtonsoft.Json for new applications. - -### Issues Addressed - -- Added net8.0, net9.0, net10.0 explicit support. -- Updated MessagePack serializer to 3.1.7 -- Updated Newtonsoft.Json to 14.0.4 -- Refactor performance benchmarks into focused, low-noise scenarios [#523](https://github.com/NEventStore/NEventStore/issues/523) -- Add a performance validation matrix for optimization workstreams [#524](https://github.com/NEventStore/NEventStore/issues/524) -- Optimize commit-attempt construction in `OptimisticEventStream` [#525](https://github.com/NEventStore/NEventStore/issues/525) -- Rework in-memory persistence reads to use direct indexes instead of full-store scans [#526](https://github.com/NEventStore/NEventStore/issues/526) -- Replace scan-heavy stream-head and snapshot lookups in the in-memory engine [#527](https://github.com/NEventStore/NEventStore/issues/527) -- Replace linked-list event storage in `OptimisticEventStream` with allocation-efficient list storage [#528](https://github.com/NEventStore/NEventStore/issues/528) -- Audit duplicate commit-id enforcement and document the follow-up direction for `_identifiers` [#529](https://github.com/NEventStore/NEventStore/issues/529) -- Lazily allocate `EventMessage.Headers` while preserving serializer and stream compatibility [#530](https://github.com/NEventStore/NEventStore/issues/530) -- Improve async polling catch-up and shutdown responsiveness [#531](https://github.com/NEventStore/NEventStore/issues/531) -- Expand serializer round-trip benchmarks and harden chunked-read handling in `RijndaelSerializer` [#532](https://github.com/NEventStore/NEventStore/issues/532) -- Add optional modern .NET targets to the core and polling packages [#534](https://github.com/NEventStore/NEventStore/issues/534) -- Add modern-target fast paths for serialization output buffering and async polling observer dispatch [#535](https://github.com/NEventStore/NEventStore/issues/535) -- Add optional modern .NET targets to JSON, BSON, and MessagePack serializer packages and reduce known-type lookup overhead [#536](https://github.com/NEventStore/NEventStore/issues/536) -- Establish Phase 5 benchmark baseline and regression gate policy for modern fast paths [#537](https://github.com/NEventStore/NEventStore/issues/537) -- Added System.Text.Json serializer package with Newtonsoft-compatible type metadata and cross-serializer swap verification tests [#512](https://github.com/NEventStore/NEventStore/issues/512) - -### System.Text.Json Serializer - -Added a new System.Text.Json serializer package that supports the same type metadata format as the existing Newtonsoft.Json serializer, allowing users to swap between them without needing to re-serialize their data. -WARNING: This is an experimental implementation and should be used with caution in production environments when swapping from Newtonsoft.Json, as there may be edge cases that have not yet been identified. - -Thanks to: [Matt Mills](https://github.com/arootbeer) for the implementation idea. - -## 10.1.1 - -### BugFix - -- Fixed Assemblies Version Numbers (AssemblyInfo files). - -## 10.1.0 - -- Improved `IEventStream` interface: `CommitChanges()` and `CommitChangesAsync()` now return `ICommit` instead of `void`. -- Updated MessagePack serializer to 3.1.3 - -### Breaking Changes - -- `IEventStream.CommitChanges()` and `IEventStream.CommitChangesAsync()` now return `ICommit` instead of `void`. - -## 10.0.1 - -### BugFix - -- Async Pipeline Hooks: initialization and PreCommit/PostCommit invocation bugs [#516](https://github.com/NEventStore/NEventStore/issues/516) - -## 10.0.0 - -- Async Methods to read from and write to streams (IStoreEvents, IEventStream, IPersistStreams, IPersistStreamsAsync, ICommitEventsAsync, IAccessSnapshotsAsync). [#513](https://github.com/NEventStore/NEventStore/issues/513) - - methods that read from a stream in an async way follow the Observer pattern and requires you to pass in an `IAsyncObservable` that will receive data as soon as they are available. -- Async Pipeline Hooks (IPipelineHookAsync). [#515](https://github.com/NEventStore/NEventStore/issues/515) -- AsyncPollingClient: a new polling client implementation that uses Async interfaces. [#505](https://github.com/NEventStore/NEventStore/issues/505) -- Removed the BinarySerializer (BinaryFormatter) from the core package and moved it to its own package [#510](https://github.com/NEventStore/NEventStore/issues/510) -- Improved comments and added more nullability checks. -- Minor performance improvements. -- Updated Testing Packages (NUnit, FluentAssertions, Microsoft.NET.Test and so on...). - -### Breaking Changes - -- `PersistStreamsExtensions.GetFrom(IPersistStreams, DateTime)` and `PersistStreamsExtensions.GetFromTo(IPersistStreams, DateTime, DateTime)` extension methods have been removed: they had inconsistent behavior with the other GetFrom(checkpointToken) methods, - they were getting data from the default bucket only. -- `PipelineHooksAwarePersistanceDecorator` renamed to `PipelineHooksAwarePersistStreamsDecorator`. -- `IPipelineHook.Select` method renamed to `IPipelineHook.SelectCommit`. -- `BinarySerializer` moved to its own package: `NEventStore.Serialization.Binary`. - - for net8.0+ call `AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true);` to enable unsafe BinaryFormatter usage. - - or add `true` to a PropertyGroup in your .csproj file. -- Improved many method signature with nullability annotations. -- `Wireup.With()` renamed `Wireup.Register()`. -- `OptimisticEventStream` constructors replaced by initialization functions: - - `new OptimisticEventStream(string bucketId, string streamId, ICommitEvents persistence, int minRevision, int maxRevision)` -> `new OptimisticEventStream(string bucketId, string streamId, ICommitEvents persistence).Initialize(int minRevision, int maxRevision)`. - - `new OptimisticEventStream(ISnapshot snapshot, ICommitEvents persistence, int maxRevision)` -> `new OptimisticEventStream(string bucketId, string streamId, ICommitEvents persistence).Initialize(ISnapshot snapshot, int maxRevision)`. - -## 9.2.0 - -- Updated nuget packages to include symbol packages and more information. -- Updated Newtonsoft.Bson 13.0.3 -- Added MessagePack serializer, thanks to [@pvagnozzi](https://github.com/pvagnozzi) -- Improved comments and removed some compilation warnings. - -## 9.1.1 - -- Fixed `build.ps1` script to correctly update Assembly Version number before building. -- Updated Readme with how Versioning works. - -## 9.1.0 - -- Support the following Target Frameworks only: netstandard2.0, net462. -- Updated Newtonsoft.Json 13.0.3 - -## 9.0.1 - -- Added documentation files to NuGet packages (improved intellisense support) [#496](https://github.com/NEventStore/NEventStore/issues/496) - -## 9.0.0 - -- Added support for .net 6 [#493](https://github.com/NEventStore/NEventStore/issues/493). -- Change / Optimization: Commit and CommitAttempt do not create internal read-only collections anymore, it can be useless given that we can change properties of events. -- NEventStore.Serialization.Json: accepts a JsonSerializerSettings to configure the serializer. - -## 8.0.0 - -- Added support for .net 5 [#489](https://github.com/NEventStore/NEventStore/issues/489). -- Added support for .net framework 4.6.1. -- Fixed InMemoryPersistenceEngine.AddSnapshot() behavior: adding multiple snapshots for the same tuple bucketId, streamId, streamRevision is not allowed; the updated snapshot will be ignored [#484](https://github.com/NEventStore/NEventStore/pull/484). -- Logging infrastructure switched to [Microsoft.Extensions.Logging](https://docs.microsoft.com/en-us/dotnet/core/extensions/logging) [#454](https://github.com/NEventStore/NEventStore/issues/454), [#488](https://github.com/NEventStore/NEventStore/pull/488). -- Reviewed Exception (and logging) messages: many of those that refer to a StreamId should also provide BucketId information [#480](https://github.com/NEventStore/NEventStore/issues/480) - -### Breaking Changes - -- Dropped support for .NET Framework 4.5, only .NET 4.6.1+ will be supported in 8.x. .NET Framework support will be dropped in a future revision. -- Logging switched to Microsoft.Extensions.Logging, old logging code and configuration functions have been removed. - -## 7.0.0 - -- The IPersistStreams interface got some major changes: - - Added new `GetFromTo(Int64, Int64)` and `GetFromTo(string, Int64, Int64)` methods to the IPersistStreams interface. - - Extension methods `PersistStreamsExtensions.GetFrom(DateTime)` and `PersistStreamsExtensions.GetFromTo(DateTime, DateTime)` were marked obsolete and will be removed. - - A new PersistStreamsExtensions.GetCommit(Int64) method was added to retrieve a single commit [#445](https://github.com/NEventStore/NEventStore/issues/445). -- PollingClient was moved to its own NEventStore.PollingClient NuGet package [#467](https://github.com/NEventStore/NEventStore/issues/467). -- Added more information to the DuplicateCommitException error message (StreamId and BucketId), also the information provided by the Persistence providers will be reviewed [#372](https://github.com/NEventStore/NEventStore/issues/372). - -### Breaking Changes - -- The default value of 0 has been removed from the `IPersistStreams.GetFrom(Int64)` method. -- Removed the almost useless `GetFromStart()` extension method: use `IPersistStream.GetFrom(0)`. -- Bson serializer was moved from NEventStore.Serialization.Json to its own package: `NEventStore.Serialization.Bson`. Closes: [#479](https://github.com/NEventStore/NEventStore/issues/479). -- PollingClient was moved to its own package: add a reference to NEventStore.PollingClient NuGet package. Also the namespace was changed from NEventStore.Client to NEventStorePollingClient. - -## 6.1.0 - -Enlist in ambient transaction has been removed from the mail library and added to the persistence drivers implementations, each driver has its own way to support, enable or disable the feature. As of now this change will mainly impact Microsoft SQL Server users, because all other persistence plugins didn't use transactions at all. - -All the transactions (or their suppression) should be explicitly managed by the user. - -Minor optimizations were made if no pipeline hooks are used. - -### Breaking Changes - -- `PipelineHookBase`: changed the way the Dispose pattern was implemented to be compliant with the framework guidelines. Move all the Dispose logic to the overridden Dispose(bool disposing) method of your pipeline hook class. -- `OptimisticPipelineHook` optimization is not configured and enabled by default (if not enlisting in ambient transactions) anymore; it now must be explicitly enabled calling UseOptimisticPipelineHook() when configuring NEventStore. Do not use it if you plan to use transactions. To restore the previous behavior call .UseOptimisticPipelineHook() when configuring NEventStore. -- `EnlistInAmbientTransaction` has been removed from the core NEventStore library. It will be added to specific persistence drivers implementations. - -## 6.0.0 - -__Version 6.x is not backwards compatible with version 5.x.__ Updating to NEventStore 6.x without doing some preparation work will result in problems. - -### New Features - -- dotnet standard 2.0 , dotnet core 2.0 are now supported for the following projects: NEventStore, NEventStore.Domain, NEventStore.Persistence.Sql, NEventStore.Persistence.MongoDb - -### Breaking Changes - -- **Removed Dispatcher and dispatching mechanic, use the PollingClient**: it was marked obsolete in the version 5.x, you should dispatch events with other mechanisms, like using a PollingClient. -More information on this topic in the issue: [Race condition in sync and async dispatchers can result in subscribers getting commits / events out of order](https://github.com/NEventStore/NEventStore/issues/360). -- **Removed LongCheckpoint class**: checkpoint now is a plain Int64, there is no need to keep a LongCheckpoint class anymore. -- **PollingClient was removed because it used to depend on Rx**: you can [read more information here](src/NEventStore/Client/README.MD). The new polling client class is called PollingClient2, this however should be considered as a sample implementation you can use to derive your own. -- **JsonSerializer and BsonSerializer were moved in a separate assembly**: if you need them, you should reference the NEventStore.Serialization.Json assembly or implement your own serializers that depend on the Json.Net version you need. -- **EventMessage** class is now sealed. -- **`OptimisticEventStream` throws exceptions if a null message or a message with null body is added to the stream**. Previously if you called Add with null event message or add with an event message with null body, the add operation was ignored without any warning or error. - -## 6.0.0-rc-1 - -New features: - -- improved logging performances ([#468](https://github.com/NEventStore/NEventStore/issues/468)). - -Bug fixed: - -- adding events in the middle of a commit should throw ConcurrencyException ([#420](https://github.com/NEventStore/NEventStore/issues/420)). - -## 6.0.0-rc-0 - -__Version 6.x is not backwards compatible with version 5.x.__ Updating to NEventStore 6.x without doing some preparation work will result in problems. - -### New Features - -- dotnet standard 2.0 , dotnet core 2.0 are now supported for the following projects: NEventStore, NEventStore.Domain, NEventStore.Persistence.Sql, NEventStore.Persistence.MongoDb - -### Breaking changes - -- **Removed Dispatcher and dispatching mechanic, use the PollingClient**: it was marked obsolete in the version 5.x, you should dispatch events with other mechanisms, like using a PollingClient. -More information on this topic in the issue: [Race condition in sync and async dispatchers can result in subscribers getting commits / events out of order](https://github.com/NEventStore/NEventStore/issues/360). -- **Removed LongCheckpoint class**: checkpoint now is a plain Int64, there is no need to keep a LongCheckpoint class anymore. -- **PollingClient was removed because it used to depend on Rx**: you can [read more information here](src/NEventStore/Client/README.MD). The new polling client class is called PollingClient2, this however should be considered as a sample implementation you can use to derive your own. -- **JsonSerializer and BsonSerializer were moved in a separate assembly**: if you need them, you should reference the NEventStore.Serialization.Json assembly or implement your own serializers that depend on the Json.Net version you need. -- **EventMessage** class is now sealed. -- **OptimisticEventStream throws exceptions if a null message or a message with null body is added to the stream**. Previously if you called Add with null event message or add with an event message with null body, the add operation was ignored without any warning or error. - -### Other Notes - -All persistence providers: - -- [MongoDb](https://github.com/NEventStore/NEventStore.Persistence.MongoDB) -- [Sql](https://github.com/NEventStore/NEventStore.Persistence.SQL) -- [RavenDb](https://github.com/NEventStore/NEventStore.Persistence.RavenDB) - currently not maintained anymore. - -are now hosted in their own project. - -Common Domain is now moved in its [own repository](https://github.com/NEventStore/NEventStore.Domain). - -## 5.x.x - -Note: Version 5 is not backwards compatible with v4. Updating to v5 without doing some preparation work will result in problems. - -### Breaking Changes - -1. Underlying schema has changed for all v5 storage engines. In order to migrate a store from v4 to v5 use NEventStore.Migrations -1.The concept of a 'Bucket' has been added as a container for streams allowing multi-tenancy, partitions, multiple-bounded contexts, sagas, etc to be stored in the one store. The API changes have been such that, using extension methods, operations will work on the default bucket, unless a bucket Id has been explicitly supplied. This should mean minimal code changes for the user. -1.Stream Ids are now string based and are limited to 1000 characters. -In the SQL engines the stream Id's are limited to 40 characters and are hashed versions of the actual StreamId. -The hashing function can be overridden during wire-up. - -### New Features - -#### Polling Client - -As an alternative to the dispatcher mechanism and improved replay / catch-up story we have implemented a CheckpointNumber in the stores that guarantees ordering across the streams. This number is guaranteed to increment but not guaranteed to be sequential. This allows you to get all Commits from a specific checkpoint and observe new ones. This implementation is polling based (and thus works for all engines) so it doesn't have the same low-latency attributes of the dispatcher mechanism. You can see how to use it here: [https://gist.github.com/damianh/6370328](https://gist.github.com/damianh/6370328) .In this, instead of the store tracking what has been dispatched, the onus is on the client to track what it has seen. And upon restart, start subscribing from what it last saw. - -In the future I'd like to see / implement reactive clients that leverage stores that are observable. - -### Other Notes - -1. Only SQL and MongoDB persistence engines are supported in this release. RavenDB engine will be shipped later. -1. RavenDB and MongoDB persistence engines are now in their own repositories and will have be shipped independently. diff --git a/GitVersion.yml b/GitVersion.yml deleted file mode 100644 index 6f83d0b51..000000000 --- a/GitVersion.yml +++ /dev/null @@ -1,14 +0,0 @@ -workflow: GitFlow/v1 -mode: ContinuousDeployment -branches: - release: - regex: ^release[/-] - increment: None # Do not bump the version automatically, previus value: Patch - hotfix: - regex: ^hotfix[/-] - increment: None # previous value: Patch - unknown: - increment: Patch -ignore: - sha: [] -merge-message-formats: {} diff --git a/Readme.md b/Readme.md deleted file mode 100644 index 3a38a2f22..000000000 --- a/Readme.md +++ /dev/null @@ -1,62 +0,0 @@ -NEventStore -=== - -NEventStore is a persistence library used to abstract different storage implementations when using event sourcing as storage mechanism. - -This library is developed with a specific focus on [DDD](http://en.wikipedia.org/wiki/Domain-driven_design)/[CQRS](https://en.wikipedia.org/wiki/Command%E2%80%93query_separation#Command_query_responsibility_segregation) applications. - -NEventStore currently supports: - -- .net standard 2.0 -- .net framework 4.6.2 - -Starting from Version 6.0.0 NEventStore will use [Semantic Versioning](https://semver.org/) to track the version numbers. - -Build Status (AppVeyor) -=== - -Branches: - -- master [![CI](https://github.com/NEventStore/NEventStore/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/NEventStore/NEventStore/actions/workflows/ci.yml) -- develop [![CI](https://github.com/NEventStore/NEventStore/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/NEventStore/NEventStore/actions/workflows/ci.yml) - -Main Library Packages -=== - -- NEventStore - the core library package. -- NEventStore.Serialization.Json - Json serialization to be used with an IDocumentSerializer. -- NEventStore.Serialization.Bson - BSon serialization to be used with an IDocumentSerializer. -- NEventStore.Serialization.MsgPack - Message Pack serialization to be used with an IDocumentSerializer. -- NEventStore.PollingClient - provides an implementation for a PollingClient. - -Documentation -=== - -Please see the [documentation](https://github.com/NEventStore/NEventStore/wiki) to get started and for more information. - -ChangeLog can be [found here](https://github.com/NEventStore/NEventStore/blob/master/Changelog.md) - -### Developed with: - -[![Resharper](http://neventstore.org/images/logo_resharper_small.gif)](http://www.jetbrains.com/resharper/) -[![TeamCity](http://neventstore.org/images/logo_teamcity_small.gif)](http://www.jetbrains.com/teamcity/) -[![dotCover](http://neventstore.org/images/logo_dotcover_small.gif)](http://www.jetbrains.com/dotcover/) -[![dotTrace](http://neventstore.org/images/logo_dottrace_small.gif)](http://www.jetbrains.com/dottrace/) - -# How to build (Windows OS) - -To build the project locally on a Windows Machine: - -- Open a Powershell console in Administrative mode and run the build script `build.ps1` in the root of the repository. - -## Versioning - -Versioning is done automatically by the build script updating the -AssemblyInfo.cs file (false in .csproj files) -before the build starts. The version number is retrieved -from the git repository tags using "gitversion" tool. - -Things are handled this way because NEventStore is used a submodule in other projects and it -need to have it's own version number when building other projects. - -You should not update the version number manually, not commit the updated AssemblyInfo files. \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 10340e1d7..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,69 +0,0 @@ -version: 1.0.{build} -image: Visual Studio 2022 -configuration: Release -assembly_info: - patch: true - file: '**\AssemblyInfo.*' - assembly_version: '{version}' - assembly_file_version: '{version}' - assembly_informational_version: '{version}' -dotnet_csproj: - patch: true - file: '**\*.csproj' - version: '{version}' - version_prefix: '{version}' - package_version: '{version}' - assembly_version: '{version}' - file_version: '{version}' - informational_version: '{version}' -install: -- ps: >- - $dotnetInstall = Join-Path $env:TEMP 'dotnet-install.ps1' - - Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile $dotnetInstall - - & $dotnetInstall -Channel 10.0 -Quality preview -InstallDir "$env:USERPROFILE\.dotnet" - - if ($LASTEXITCODE -ne 0) { - throw 'Failed to install .NET 10 preview SDK.' - } - - $env:PATH = "$env:USERPROFILE\.dotnet;$env:PATH" - - [Environment]::SetEnvironmentVariable('PATH', $env:PATH, 'Process') - - dotnet --list-sdks -- ps: dotnet tool restore -before_build: -- ps: >- - # Display SDK information - - dotnet --info - - # Display minimal restore text - - dotnet restore ./src/NEventStore.Core.sln --verbosity m - - dotnet tool run dotnet-gitversion /l console /output buildserver /updateAssemblyInfo -build: off -build_script: -- ps: dotnet build ./src/NEventStore.Core.sln -c $env:CONFIGURATION --no-restore -p:ContinuousIntegrationBuild=True -after_build: -- ps: >- - dotnet pack ./src/NEventStore/NEventStore.Core.csproj -c $env:CONFIGURATION --no-build -o artifacts /p:PackageVersion=$env:GitVersion_SemVer - - dotnet pack ./src/NEventStore.PollingClient/NEventStore.PollingClient.csproj -c $env:CONFIGURATION --no-build -o artifacts /p:PackageVersion=$env:GitVersion_SemVer - - dotnet pack ./src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj -c $env:CONFIGURATION --no-build -o artifacts /p:PackageVersion=$env:GitVersion_SemVer - - dotnet pack ./src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj -c $env:CONFIGURATION --no-build -o artifacts /p:PackageVersion=$env:GitVersion_SemVer - - dotnet pack ./src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj -c $env:CONFIGURATION --no-build -o artifacts /p:PackageVersion=$env:GitVersion_SemVer - - dotnet pack ./src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj -c $env:CONFIGURATION --no-build -o artifacts /p:PackageVersion=$env:GitVersion_SemVer -test: off -test_script: -- ps: dotnet test ./src/NEventStore.Core.sln -c $env:CONFIGURATION --no-build -artifacts: -- path: '**\artifacts\**\*.*' -deploy: off \ No newline at end of file diff --git a/build.ps1 b/build.ps1 deleted file mode 100644 index 75dcdbc59..000000000 --- a/build.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -$configurationdefault = "Release" -$artifacts = "../../artifacts" - -$configuration = Read-Host 'Configuration to build [default: Release] ?' -if ($configuration -eq '') { - $configuration = $configurationdefault -} -$runtests = Read-Host 'Run Tests (y / n) [default:n] ?' - -# Install gitversion tool -dotnet tool restore - -# Display minimal restore information -dotnet restore ./src/NEventStore.Core.sln --verbosity m - -# GitVersion -$str = dotnet tool run dotnet-gitversion /updateAssemblyInfo | out-string -$json = convertFrom-json $str -$nugetversion = $json.SemVer - -# Build -Write-Host "Building: "$nugetversion -dotnet build ./src/NEventStore.Core.sln -c $configuration --no-restore -p:ContinuousIntegrationBuild=True - -# Testing -if ($runtests -eq "y") { - Write-Host "Executing Tests" - dotnet test ./src/NEventStore.Core.sln -c $configuration --no-build - Write-Host "Tests Execution Complated" -} - -# NuGet packages -Write-Host "NuGet Packages creation" -dotnet pack ./src/NEventStore/NEventStore.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion -dotnet pack ./src/NEventStore.PollingClient/NEventStore.PollingClient.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion -dotnet pack ./src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion -dotnet pack ./src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion -dotnet pack ./src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion -dotnet pack ./src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion -dotnet pack ./src/NEventStore.Serialization.SystemTextJson/NEventStore.Serialization.SystemTextJson.Core.csproj -c $configuration --no-build -o $artifacts /p:PackageVersion=$nugetversion -Write-Host "NuGet Packages creation completed" \ No newline at end of file diff --git a/docs/Performance-Benchmarks.md b/docs/Performance-Benchmarks.md deleted file mode 100644 index ce8192c64..000000000 --- a/docs/Performance-Benchmarks.md +++ /dev/null @@ -1,68 +0,0 @@ -# Performance Benchmarks - -This document defines the focused benchmark regression set introduced for performance foundation issue `#523`. - -For the correctness-validation plan that maps these benchmarks to the later performance workstreams, see [Performance-Validation-Matrix.md](Performance-Validation-Matrix.md). -For the Phase 5 regression gate policy and review format, see [Performance-Phase5-Regression-Gates.md](Performance-Phase5-Regression-Gates.md). - -## Regression Set - -The benchmark project now covers these scenario groups: - -- `StreamOpenBenchmarks`: empty stream open, populated stream open, sync and async. -- `StreamWriteBenchmarks`: batched append-and-commit loops for smaller single-event commit scenarios, sync and async. -- `StreamWriteLargeBenchmarks`: batched append-and-commit loops for larger single-event commit scenarios, sync and async. -- `CommitAttemptBenchmarks`: commit-attempt construction cost across event and header counts. -- `InMemoryReadBenchmarks`: global checkpoint reads, bucket checkpoint reads, and per-stream revision reads. -- `AsyncPollingBenchmarks`: idle poll cost and catch-up poll cost through `AsyncPollingClient`. -- `SerializerRoundTripBenchmarks`: serializer-only round trips for binary, BSON, JSON, MessagePack, gzip wrappers, and Rijndael wrappers across small metadata-heavy and larger body-heavy payloads. - -## Running - -Build the benchmark project from the solution root: - -```powershell -dotnet build .\src\NEventStore.Benchmark\NEventStore.Benchmark.csproj -c Release -``` - -List the available benchmarks: - -```powershell -dotnet .\src\NEventStore.Benchmark\bin\Release\net8.0\NEventStore.Benchmark.dll --list flat -``` - -Run the full regression set: - -```powershell -dotnet .\src\NEventStore.Benchmark\bin\Release\net8.0\NEventStore.Benchmark.dll -``` - -Run the full regression set as one joined report for PR review: - -```powershell -dotnet .\src\NEventStore.Benchmark\bin\Release\net8.0\NEventStore.Benchmark.dll --filter * --join -``` - -Run a focused subset: - -```powershell -dotnet .\src\NEventStore.Benchmark\bin\Release\net8.0\NEventStore.Benchmark.dll --filter *InMemoryReadBenchmarks* -``` - -## Baseline Outputs - -BenchmarkDotNet artifacts are written to: - -`artifacts/benchmarks/` - -The generated baseline reports are exported under: - -`artifacts/benchmarks/results/` - -The Markdown exporter is intended to be the review-friendly baseline for before/after comparisons, while the CSV and HTML exporters provide machine-readable and richer local inspection formats. - -Raw files under `artifacts/` are intentionally ignored by git. Curated baselines that should travel with the repository live under: - -`docs/performance-baselines/` - -When refreshing a checked-in baseline, keep the raw BenchmarkDotNet files locally and copy only the selected gate rows, command, environment, and source report names into the curated baseline document. diff --git a/docs/Performance-DuplicateCommitId-Audit.md b/docs/Performance-DuplicateCommitId-Audit.md deleted file mode 100644 index 4596728e9..000000000 --- a/docs/Performance-DuplicateCommitId-Audit.md +++ /dev/null @@ -1,88 +0,0 @@ -# Duplicate Commit-Id Audit - -This note closes the audit scope for issue `#529`. - -## Objective - -Determine whether duplicate commit IDs are already enforced by the persistence layer before changing the stream-level `_identifiers` tracking in `OptimisticEventStream`. - -## Finding Summary - -All currently supported persistence providers audited here enforce duplicate commit IDs at the persistence boundary with the same effective scope: `BucketId + StreamId + CommitId`. - -That means `_identifiers` is not the correctness boundary for duplicate detection. It is only an in-memory fast-fail cache for commit IDs that happened to be loaded into the current `OptimisticEventStream` instance. - -## Provider Evidence - -### Core in-memory provider - -- [InMemoryPersistenceEngine.cs](C:/Work/NEventStore/NEventStore/src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs) stores duplicate identities in `_potentialDuplicates`. -- The duplicate identity comparer includes `BucketId`, `StreamId`, and `CommitId`, so duplicate detection is scoped per stream inside a bucket. -- `DetectDuplicate(CommitAttempt attempt)` throws `DuplicateCommitException` before the commit is inserted. -- The core acceptance suite already asserts duplicate rejection for: - - persisting the same commit twice - - reusing a commit ID on the same stream - -### MongoDB provider - -- `NEventStore.Persistence.MongoDB` creates a unique `CommitId_Index` on `BucketId + StreamId + CommitId` in `MongoPersistenceEngine`. -- Both sync and async persistence engines convert that index conflict into `DuplicateCommitException`. -- The MongoDB test project links the shared `PersistenceTests.cs` and `PersistenceTests.Async.cs` acceptance suites from the core repo, so the duplicate-commit acceptance scenarios are part of the provider test surface. - -### SQL provider family - -- `NEventStore.Persistence.SQL` creates a unique commit-ID index on `BucketId + StreamId + CommitId` across every shipped dialect: - - MsSql - - MySql - - PostgreSql - - Sqlite - - Oracle -- Both sync and async SQL persistence engines catch `UniqueKeyViolationException`, run an explicit duplicate check by `BucketId + StreamId + CommitId`, and then throw `DuplicateCommitException`. -- The SQL provider test projects link the shared `PersistenceTests.cs` and `PersistenceTests.Async.cs` acceptance suites, so duplicate-commit acceptance scenarios are exercised across the supported SQL backends. - -## Stream-Level Implication - -`OptimisticEventStream._identifiers` is incomplete by design: - -- it only knows about commit IDs for commits that were loaded into the current stream instance -- it does not represent the full stream history after reopening from the middle of a stream -- it does not help at all once a duplicate commit ID exists outside the loaded history window - -So the persistence layer is already the real source of truth for duplicate commit detection. The stream-level cache can only provide an earlier exception for the subset of duplicate IDs that were previously read into memory. - -## Decision - -The recommended follow-up direction is: - -- remove `_identifiers` from `OptimisticEventStream` in a separate implementation issue - -Reasoning: - -- duplicate commit-ID correctness is already enforced by the audited persistence providers -- the current cache is not a complete duplicate detector after partial-history reads -- removing it aligns the implementation with where correctness already lives and removes per-stream memory growth proportional to loaded commit count - -This audit issue does not make that change. It only establishes that the follow-up change is defensible. - -## Required Follow-Up Tests Before Removal - -If `_identifiers` is removed, add these tests before or with that implementation: - -### Core stream tests - -- Sync: reopen a fully loaded stream and attempt `CommitChanges` with a previously persisted commit ID; assert `DuplicateCommitException` is still surfaced when the persistence layer rejects it. -- Async: same scenario for `CommitChangesAsync`. -- Sync: reopen from a later revision so the original duplicate commit ID is not in the loaded history window, then attempt `CommitChanges` with that earlier commit ID; assert `DuplicateCommitException`. -- Async: same scenario for `CommitChangesAsync`. - -### Provider-backed acceptance tests - -- Sync: exercise duplicate commit IDs through `IStoreEvents.OpenStream` after reopening a stream from the beginning. -- Async: same scenario through `OpenStreamAsync`. -- Sync: exercise duplicate commit IDs after reopening from a later revision to prove the persistence layer, not the stream cache, is preserving the behavior. -- Async: same scenario through `OpenStreamAsync`. - -## Recommendation Status - -- Issue `#529`: complete -- `_identifiers` removal: approved as a follow-up optimization, not part of this audit issue diff --git a/docs/Performance-GitHub-Issue-Drafts.md b/docs/Performance-GitHub-Issue-Drafts.md deleted file mode 100644 index c51dc5b26..000000000 --- a/docs/Performance-GitHub-Issue-Drafts.md +++ /dev/null @@ -1,391 +0,0 @@ -# Performance GitHub Issue Drafts - -These issue drafts are derived from [Performance-SPEC-Input.md](C:/Work/NEventStore/NEventStore/docs/Performance-SPEC-Input.md). - -GitHub issue creation was attempted with `gh issue create`, but the authenticated token does not currently have permission to create issues for `NEventStore/NEventStore`. The exact error was: - -`GraphQL: Resource not accessible by personal access token (createIssue)` - -Once the token has issue-creation permission, the issues below can be created directly. - -## 1. Refactor performance benchmarks into focused, low-noise scenarios - -### Title -`Refactor performance benchmarks into focused, low-noise scenarios` - -### Body -## Objective -Refactor the benchmark suite so it provides focused, low-noise measurements that can be used to validate each performance change in the program. - -## Scope -- Split the current broad persistence benchmark into focused scenarios for: - - stream open/read - - commit attempt construction - - in-memory global checkpoint reads - - in-memory stream revision reads - - polling catch-up and idle behavior - - serializer-only round trips -- Pre-generate commit IDs and event payloads during setup to remove benchmark-side noise. -- Define the benchmark scenarios that will be used as the regression set for later performance work. -- Save baseline benchmark outputs in a repeatable location. - -## Out of Scope -- Changing library behavior. -- Adding CI benchmark gating in this issue. -- Changing compatibility targets for core library projects. - -## Required Validation -- Existing benchmark project must still run successfully. -- Baseline reports must exist for the scenarios listed above. - -## Completion Criteria -- Benchmark results isolate subsystem cost well enough to support the rest of the performance plan. -- Baseline reports are available for future before/after comparisons. - ---- - -## 2. Define test-validation matrix for each performance workstream - -### Title -`Define test-validation matrix for each performance workstream` - -### Body -## Objective -Define the correctness validation plan for every performance workstream so no optimization lands without explicit test coverage. - -## Scope -- Identify the existing tests that already validate: - - ordering - - stream revision and commit sequence invariants - - duplicate detection - - partial stream behavior - - snapshot behavior - - polling semantics - - serializer round trips -- Identify missing tests that must be added with each implementation issue. -- Produce a test-validation matrix that later issues can reference directly. - -## Out of Scope -- Implementing the performance changes. -- Reworking the benchmark suite beyond what is needed to map validation. - -## Completion Criteria -- Every planned implementation issue has an explicit correctness-validation path. -- No later issue remains ambiguous about whether existing tests are sufficient. - ---- - -## 3. Reduce hot-path LINQ and commit construction allocations without behavior changes - -### Title -`Reduce hot-path LINQ and commit construction allocations without behavior changes` - -### Body -## Objective -Land the safest internal allocation and enumeration improvements first, without changing externally observable behavior. - -## Scope -- Replace clearly hot LINQ/materialization paths with explicit loops where behavior is straightforward and testable. -- Optimize commit-attempt construction in `OptimisticEventStream` to reduce unnecessary `ToArray` and `ToDictionary` costs. -- Pre-size internal collections where counts are already known. -- Preserve public APIs and current behavior exactly. - -## Out of Scope -- Structural re-indexing of the in-memory persistence engine. -- Replacing `LinkedList` in `OptimisticEventStream`. -- Changing duplicate commit tracking or `EventMessage.Headers` semantics. - -## Dependencies -- Benchmark foundation issue -- Validation matrix issue - -## Required Validation -- Run the existing tests that cover the affected code paths. -- Add tests if loop-based replacements or copy reductions affect logic not already explicitly covered. -- Record before/after results for the relevant benchmark micro-scenarios. - -## Completion Criteria -- At least one targeted benchmark improves measurably. -- No correctness regression is introduced. - ---- - -## 4. Rework in-memory persistence reads to use direct indexes instead of full-store scans - -### Title -`Rework in-memory persistence reads to use direct indexes instead of full-store scans` - -### Body -## Objective -Eliminate repeated full-store flatten/filter/order/materialize behavior from in-memory checkpoint and stream read paths. - -## Scope -- Add direct indexing for: - - global checkpoint reads - - bucket checkpoint reads - - per-stream revision reads -- Rework in-memory read paths to avoid repeated full-store scans and repeated sorting on hot paths. -- Preserve checkpoint ordering and range-boundary semantics exactly. - -## Out of Scope -- Polling client changes. -- Serializer changes. -- Public API changes. - -## Dependencies -- Benchmark foundation issue -- Validation matrix issue -- Low-risk hot-path cleanup issue - -## Required Validation -- Existing acceptance and unit tests must pass. -- Add or confirm tests for ordering, range boundaries, and per-stream revision filtering. -- Record before/after benchmarks for global checkpoint reads, bucket checkpoint reads, and stream revision reads. - -## Completion Criteria -- Large-commit-count checkpoint and stream read scenarios show clear improvement. -- Behavior remains identical under tests. - ---- - -## 5. Replace scan-heavy stream-head and snapshot lookups in the in-memory engine - -### Title -`Replace scan-heavy stream-head and snapshot lookups in the in-memory engine` - -### Body -## Objective -Finish the in-memory engine performance work by removing scan-heavy supporting structures for stream heads, snapshots, delete, and purge. - -## Scope -- Replace scan-heavy stream-head lookup structures with direct lookup structures. -- Replace scan-heavy snapshot lookup structures with direct lookup structures. -- Improve delete/purge behavior to avoid repeated array creation and repeated collection scans where safe. -- Preserve snapshot selection and stream-head behavior exactly. - -## Out of Scope -- Global checkpoint indexing beyond the main read-path issue. -- Changes to core stream internals. - -## Dependencies -- Benchmark foundation issue -- Validation matrix issue -- In-memory read-path rework issue - -## Required Validation -- Add or confirm tests for snapshot selection, duplicate snapshot handling, stream head updates, delete stream, purge bucket, and purge store. -- Record before/after benchmarks for scenarios influenced by these supporting structures where measurable. - -## Completion Criteria -- Supporting lookup paths are no longer dominated by linear scans. -- Snapshot and delete/purge semantics remain correct under tests. - ---- - -## 6. Replace linked-list event storage in OptimisticEventStream with allocation-efficient list storage - -### Title -`Replace linked-list event storage in OptimisticEventStream with allocation-efficient list storage` - -### Body -## Objective -Reduce stream materialization and write-path overhead by replacing linked-list-based internal event storage with allocation-efficient list storage. - -## Scope -- Replace internal `LinkedList` usage in `OptimisticEventStream` with `List`. -- Preserve the public `ICollection` contract for committed and uncommitted event exposure. -- Pre-size internal collections when counts are known. -- Reduce unnecessary internal copies where safe. -- Preserve stream revision, commit sequence, partial-stream, and sync/async behavior exactly. - -## Out of Scope -- Removing duplicate commit-ID tracking. -- Changing `EventMessage.Headers` semantics. -- Public API changes. - -## Dependencies -- Benchmark foundation issue -- Validation matrix issue -- Low-risk hot-path cleanup issue - -## Required Validation -- Add or confirm tests for open empty stream, open populated stream, append and clear behavior, commit with one and many events, partial stream reads, and revision/commit sequence invariants. -- Record before/after benchmarks for `ReadFromStream`, `ReadFromStreamAsync`, and the relevant write scenarios. - -## Completion Criteria -- Stream-read allocations and latency improve measurably. -- Stream semantics remain unchanged under tests. - ---- - -## 7. Audit duplicate commit-id enforcement before changing stream-level identifier tracking - -### Title -`Audit duplicate commit-id enforcement before changing stream-level identifier tracking` - -### Body -## Objective -Audit whether duplicate commit IDs are already enforced by persistence implementations before changing stream-level identifier tracking. - -## Scope -- Review all supported persistence implementations for duplicate commit ID guarantees. -- Decide one of: - - keep current `_identifiers` tracking in `OptimisticEventStream` - - remove it universally - - make it provider-driven or delayed -- Identify the exact tests required for any follow-up implementation. - -## Out of Scope -- Implementing the change before the audit is complete. - -## Dependencies -- Validation matrix issue - -## Required Validation -- Provider-by-provider evidence is documented. -- Follow-up tests are identified for the chosen direction. - -## Completion Criteria -- The issue ends with a decision-complete recommendation or an explicit decision not to proceed. - ---- - -## 8. Review lazy header allocation for EventMessage without breaking compatibility - -### Title -`Review lazy header allocation for EventMessage without breaking compatibility` - -### Body -## Objective -Determine whether `EventMessage.Headers` can be made lazy without breaking caller expectations, serializer behavior, or compatibility. - -## Scope -- Audit how `EventMessage.Headers` is used across core code, tests, and serializers. -- Decide whether a safe lazy-allocation design exists while preserving current public semantics. -- Identify the exact tests required for any follow-up implementation. - -## Out of Scope -- Shipping the change without an explicit compatibility-safe design. - -## Dependencies -- Validation matrix issue - -## Required Validation -- Evidence is collected for mutability expectations, non-null expectations, and serializer round-trip behavior. -- Follow-up tests are identified for the chosen direction. - -## Completion Criteria -- The issue ends with either an implementation-ready recommendation or an explicit rejection due to compatibility risk. - ---- - -## 9. Improve polling client efficiency for catch-up, idle waits, and shutdown - -### Title -`Improve polling client efficiency for catch-up, idle waits, and shutdown` - -### Body -## Objective -Improve polling client operational efficiency after the core stream and in-memory work is complete. - -## Scope -- Reduce unnecessary delay after successful progress. -- Track and await worker task completion during shutdown instead of polling. -- Preserve stop, retry-on-hole, cancellation, and observer semantics. -- Coordinate with existing related issues `#446` and `#425` rather than ignoring their prior context. - -## Out of Scope -- Changing polling client public surface as part of this issue. -- Folding serializer work into the same issue. - -## Dependencies -- Benchmark foundation issue -- Validation matrix issue -- Low-risk hot-path cleanup issue -- In-memory read-path rework issue -- Stream internal storage rework issue - -## Required Validation -- Add or confirm tests for cancellation, shutdown, stop behavior, retry-on-hole, and backpressure. -- Record benchmarks or stress measurements for idle polling overhead and catch-up throughput. - -## Completion Criteria -- Polling overhead is reduced or catch-up latency improves measurably. -- Existing polling semantics remain correct under tests. - ---- - -## 10. Add serializer benchmarks and review byte-handling copies in serialization utilities - -### Title -`Add serializer benchmarks and review byte-handling copies in serialization utilities` - -### Body -## Objective -Add serializer-specific measurements and then review byte-handling/copy-heavy utilities with compatibility preserved. - -## Scope -- Add serializer-only benchmarks for supported serializers and wrappers. -- Review `MemoryStream` and copy-heavy utility paths after serializer baselines exist. -- Preserve serializer interfaces, wire formats, and defaults unless a separate approval is taken. - -## Out of Scope -- Changing serializer defaults. -- Bundling unrelated serializer feature work into this issue. - -## Dependencies -- Benchmark foundation issue -- Validation matrix issue -- Low-risk hot-path cleanup issue -- In-memory read-path rework issue -- Stream internal storage rework issue - -## Required Validation -- Add or confirm serializer round-trip and compatibility-sensitive tests. -- Record before/after serializer benchmark results for the affected utilities. - -## Completion Criteria -- Serializer baselines exist and any optimization is benchmark-backed. -- No compatibility regression is introduced. - ---- - -## 11. Performance optimization program: benchmarks, core hot paths, and validation - -### Title -`Performance optimization program: benchmarks, core hot paths, and validation` - -### Body -## Objective -Track the full NEventStore performance optimization program from benchmark cleanup through compatibility-safe core optimizations and follow-up investigations. - -## Milestone Order -1. Benchmark foundation and validation matrix. -2. Low-risk hot-path cleanup. -3. In-memory persistence indexing and supporting-structure rework. -4. `OptimisticEventStream` internal storage rework. -5. Investigation-gated correctness-sensitive changes. -6. Polling and serializer follow-up. -7. Optional modern fast paths are deferred and not part of the first milestone. - -## Child Issues -- [ ] Benchmark foundation -- [ ] Validation matrix -- [ ] Low-risk hot-path cleanup -- [ ] In-memory read-path rework -- [ ] In-memory supporting-structure rework -- [ ] `OptimisticEventStream` internal storage rework -- [ ] Duplicate commit-ID tracking audit -- [ ] Lazy header-allocation audit -- [ ] Polling client efficiency follow-up -- [ ] Serializer benchmark and byte-handling follow-up - -## Program Rules -- Core compatibility remains `netstandard2.0` and `net462`. -- No public API break is allowed in the main performance program. -- Every implementation change must be validated by tests and by before/after benchmarks from the cleaned benchmark suite. -- Investigation issues do not automatically become implementation work until their recommendations are decision-complete. - -## Completion Criteria -- All child issues are resolved or explicitly closed with documented decisions. -- Program summary captures benchmark deltas and correctness-validation outcomes for each implemented workstream. diff --git a/docs/Performance-Phase5-Regression-Gates.md b/docs/Performance-Phase5-Regression-Gates.md deleted file mode 100644 index f9e819682..000000000 --- a/docs/Performance-Phase5-Regression-Gates.md +++ /dev/null @@ -1,63 +0,0 @@ -# Phase 5 Benchmark Regression Gates - -This document defines the benchmark governance for Phase 5 optional modern fast paths in umbrella issue `#546`, starting with issue `#537`. - -The goal is to keep benchmark evidence reviewable without making noisy microbenchmarks hard CI blockers before the project has enough repeated-run data. - -## Baseline Artifacts - -Checked-in baseline artifacts live under: - -`docs/performance-baselines/` - -The current Phase 5 seed baseline is: - -- [phase5-modern-fastpaths-2026-04-20.md](performance-baselines/phase5-modern-fastpaths-2026-04-20.md) - -Raw BenchmarkDotNet outputs remain under `artifacts/benchmarks/results/` and are intentionally ignored by git. The checked-in baseline records the source report names so a reviewer can reproduce the raw Markdown, CSV, and HTML files locally. - -## Reproduction Command - -Run from the solution root after building the benchmark project in Release: - -```powershell -dotnet build .\src\NEventStore.Benchmark\NEventStore.Benchmark.csproj -c Release -dotnet .\src\NEventStore.Benchmark\bin\Release\net8.0\NEventStore.Benchmark.dll --filter * --join -``` - -Use the joined report for gate comparisons because it preserves one machine/environment header and one comparable table across all benchmark classes. - -## Gate Scenarios - -These scenarios are the minimal Phase 5 gate set. They intentionally cover the code paths affected by modern target additions and current fast-path work rather than every benchmark parameter combination. - -- `SerializerRoundTripBenchmarks` with `EventCount=100` and `PayloadSizeBytes=4096`: representative allocation-heavy serializer payloads across binary, BSON, JSON, MessagePack, gzip, and Rijndael wrappers. -- `AsyncPollingBenchmarks.IdlePollAsync` and `CatchUpPollAsync` with `CommitCount=1000`: modern async dispatch and catch-up behavior. -- `CommitAttemptBenchmarks.ConstructCommitAttempt` with `HeaderCount=5` and `EventsPerCommit=100`: commit construction allocation pressure. -- `StreamOpenBenchmarks.OpenPopulatedStream` and `OpenPopulatedStreamAsync` with `CommitCount=100000`: read/materialization hot paths where modern runtime changes can affect iteration and collection overhead. -- `StreamWriteLargeBenchmarks.AppendSingleEventCommits` and `AppendSingleEventCommitsAsync` with `CommitCount=100000`: large write-path allocation and latency pressure. -- `InMemoryReadBenchmarks` with `CommitCount=100000`: read index and checkpoint traversal paths, used as a reference signal even though in-memory persistence is not the primary production provider. - -## Threshold Policy - -Until repeated CI or scheduled benchmark runs establish stable variance, do not fail PRs automatically on a single benchmark run. Use these review thresholds instead: - -- Treat a mean latency increase above 10% as a regression candidate when it is outside normal run noise or repeated in a second local run. -- Treat an allocation increase above 5% as a regression candidate because allocation measurements are usually less noisy than wall-clock timings. -- Treat any new Gen2 collection in a gate scenario that previously had none as a regression candidate unless the change is expected and documented. -- Ignore timing-only changes below 5% unless the same direction repeats across several related scenarios. -- Prefer the CSV exporter for calculations and the GitHub Markdown exporter for review comments. - -## Review Report Format - -Every Phase 5 performance PR should include: - -- benchmark command and commit SHA for before and after runs -- machine/runtime summary copied from the BenchmarkDotNet header -- changed gate scenarios with before mean, after mean, percent delta, before allocation, after allocation, and percent delta -- explicit note for any benchmark with high variance, outliers, or minimum-iteration warnings -- correctness validation command, usually `dotnet test .\src\NEventStore.Core.sln -c Release --no-build` - -## Exception Workflow - -A regression candidate can be accepted only when the PR explains why the tradeoff is intentional. Common acceptable reasons are compatibility fixes, correctness fixes, or improved behavior in a more important paired scenario. The explanation must name the affected gate row and link to the supporting test or benchmark evidence. diff --git a/docs/Performance-SPEC-Input.md b/docs/Performance-SPEC-Input.md deleted file mode 100644 index 89aba23c3..000000000 --- a/docs/Performance-SPEC-Input.md +++ /dev/null @@ -1,646 +0,0 @@ -# Performance Optimization SPEC Input - -This document collects the current performance observations, the most promising optimization opportunities, and a phased implementation strategy for NEventStore. - -The goal is not to jump directly into code changes. The goal is to produce a practical input document for a formal SPEC, with enough detail to decide scope, ordering, compatibility constraints, validation criteria, and benchmark coverage. - -## Goals - -- Improve throughput and reduce allocations in the most frequently used paths. -- Reduce latency for stream reads, commit writes, and polling/catch-up reads. -- Improve the quality of the benchmark suite so it measures library cost more accurately. -- Modernize the implementation where useful without sacrificing maximum compatibility. -- Require every implementation change to be validated by tests, using existing coverage when sufficient and adding new tests when it is not. -- Preserve the current public programming model unless a change has a strong payoff and a low migration cost. - -## Non-Goals - -- Rewriting the entire library around a different storage abstraction. -- Removing support for existing target frameworks. -- Introducing large public API changes as part of the first optimization wave. -- Optimizing obscure code paths before the main event-stream, persistence, and polling hot paths. - -## Compatibility Guardrails - -The optimization plan should assume that compatibility is a hard requirement. - -- Keep the existing compatibility targets for the core package. - Current core targets are `netstandard2.0` and `net462`. -- Prefer internal implementation changes over public API changes. -- When modern runtime-specific optimizations are valuable, add them as optional fast paths behind multi-targeting rather than replacing the compatibility implementation. -- Do not make benchmark-only runtime upgrades a prerequisite for library consumers. -- Avoid changes that force downstream projects to switch serializers, persistence implementations, or target frameworks. -- Keep serialized shapes and persistence behavior stable unless an explicit compatibility review says otherwise. -- Treat test validation as mandatory for every change. A performance improvement is not complete until it is covered by existing tests or by new tests added with the change. - -## Test Validation Requirement - -Every implementation change in the future SPEC must include an explicit test validation plan. - -- No performance-oriented code change should be merged on benchmark evidence alone. -- If existing automated tests already validate the affected behavior, the implementation plan should name those tests explicitly. -- If existing tests do not cover the affected behavior closely enough, the implementation plan should add new unit, integration, acceptance, or regression tests as appropriate. -- Every phase should define both: - - performance validation through benchmarks or measurements - - correctness validation through tests -- When a change is intentionally internal and should not alter behavior, tests should prove behavioral equivalence at the public API or persistence-contract level. -- When a change affects concurrency, ordering, duplicate detection, serialization, polling, or snapshot behavior, new tests should generally be assumed necessary unless equivalent coverage already exists. - -## Current Evidence - -The repository already contains checked-in BenchmarkDotNet results for the in-memory persistence path. - -Observed shape from the existing `PersistenceBenchmarks` results on .NET 9: - -- `WriteToStream` at `100000` commits: about `385.6 ms` and `383 MB` allocated. -- `WriteToStreamAsync` at `100000` commits: about `404.9 ms` and `397 MB` allocated. -- `ReadFromStream` at `100000` commits: about `23.6 ms` and `18.8 MB` allocated. -- `ReadFromEventStore` at `100000` commits: about `2.08 ms` and `248 B` allocated. - -This already tells us two important things: - -- Stream materialization adds a large amount of extra work beyond raw commit enumeration. -- The write path is allocation-heavy and scales poorly as commit volume grows. - -The current benchmark harness also adds avoidable benchmark-side work: - -- It creates a new `Guid` per commit. -- It converts the loop index to string per event. -- It only exercises in-memory persistence, so serializer costs and other persistence implementations are not represented. - -That means the existing results are still useful, but they are not clean enough to support fine-grained optimization decisions on their own. - -## Main Optimization Workstreams - -### 1. Re-index the In-Memory Persistence Engine - -Affected area: - -- `src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs` - -Current issue: - -- Global checkpoint queries flatten all buckets, clone bucket commit arrays, filter, sort, and materialize a new array on every call. -- Per-stream queries also rely on repeated LINQ scans and materialization. -- Stream-head and snapshot lookups use linear searches over linked collections. - -Why this matters: - -- The in-memory engine is used by the benchmark suite, examples, tests, and likely some production-like scenarios. -- Polling and catch-up reads can become `O(total_commits)` per query even when the caller only needs data after one checkpoint. -- The current implementation allocates aggressively during reads because it repeatedly builds arrays and enumerates intermediate LINQ pipelines. - -Suggested direction: - -- Maintain an append-only global checkpoint index for commits. -- Maintain per-bucket and per-stream indexes for direct range scans. -- Replace linked-list-based stream-head and snapshot lookup structures with dictionaries keyed by stream identity. -- Use direct loops instead of repeated `Where`, `OrderBy`, `SelectMany`, and `ToArray` on hot paths. - -Expected impact: - -- Significant reduction in CPU time and allocation rate for `GetFrom(checkpoint)` and `GetFromTo`. -- Better scaling for polling clients and catch-up readers. -- More representative benchmark results for higher commit counts. - -Compatibility risk: - -- Low if behavior remains identical. -- Medium if ordering, duplicate detection, or snapshot selection semantics change unintentionally. - -Validation requirements: - -- Existing acceptance and unit tests must still pass. -- Changes in this area should ship with targeted tests for ordering, range boundaries, snapshot selection, and delete/purge behavior unless existing coverage is already explicit and sufficient. -- Add targeted benchmarks for: - - global checkpoint reads - - bucket checkpoint reads - - stream revision reads - - snapshot lookup - - delete/purge operations - -### 2. Replace LinkedList-Based Event Storage in OptimisticEventStream - -Affected area: - -- `src/NEventStore/OptimisticEventStream.cs` - -Current issue: - -- Committed and uncommitted events are stored in `LinkedList`. -- The write path copies uncommitted headers and events into new collections on each commit attempt. -- Stream population walks commits and events one item at a time with limited use of count-based pre-sizing. - -Why this matters: - -- `LinkedList` has poor cache locality and higher per-node overhead than `List`. -- The benchmark data strongly suggests that stream materialization is a major source of read overhead. -- Commit creation currently performs extra allocations that could be reduced substantially. - -Suggested direction: - -- Replace internal `LinkedList` storage with `List`. -- Keep the public `ICollection` contract unchanged. -- Optimize `BuildCommitAttempt` to avoid generic LINQ conversions on every commit. -- Pre-size lists and dictionaries where the sizes are already known. -- Review whether committed events need to be copied at all in some internal transitions, or whether references can be reused safely. - -Expected impact: - -- Lower per-event memory overhead. -- Faster iteration during stream population and reads. -- Reduced allocation rate in the write path. - -Compatibility risk: - -- Low if the public interfaces remain unchanged and observable behavior stays the same. -- Must verify no tests depend on linked-list-specific enumeration behavior. - -Validation requirements: - -- Add focused benchmarks for: - - open empty stream - - open populated stream - - append N events to an already-open stream - - commit with 1 event vs many events - - read committed events through the public stream API -- Add or identify tests that cover stream open, append, commit, clear, revision tracking, and partial-stream behavior. - -### 3. Remove or Reduce Duplicate Commit-ID Tracking in Stream Instances - -Affected areas: - -- `src/NEventStore/OptimisticEventStream.cs` -- persistence implementations that already enforce duplicate commit detection - -Current issue: - -- Each opened stream accumulates all prior commit IDs in a `HashSet`. -- This duplicates data that persistence engines may already validate. - -Why this matters: - -- The memory cost grows with stream history length. -- Stream opening pays an additional cost proportional to historical commit count. - -Suggested direction: - -- Verify which persistence implementations already guarantee duplicate commit ID detection. -- If the guarantee is universal, remove the stream-level `_identifiers` cache entirely. -- If the guarantee is not universal, consider making the stream-level cache optional, delayed, or provider-driven. - -Expected impact: - -- Reduced memory footprint for large streams. -- Faster stream initialization. - -Compatibility risk: - -- Medium because duplicate commit behavior is correctness-sensitive. -- This should only be changed after a provider-by-provider behavior audit. - -Validation requirements: - -- Duplicate commit tests across all supported persistence implementations. -- Benchmarks for stream initialization before and after the change. - -### 4. Make EventMessage Header Allocation Lazy - -Affected area: - -- `src/NEventStore/EventMessage.cs` - -Current issue: - -- Every `EventMessage` constructs an empty `Dictionary` even when no headers are used. - -Why this matters: - -- Event messages are a core unit of data. -- Empty dictionary allocation is pure overhead in the common case where only `Body` is set. - -Suggested direction: - -- Use lazy header initialization. -- Preserve the current public shape and semantics as much as possible. -- If the property cannot safely become nullable from a compatibility perspective, use an internal shared empty instance or a lazy backing field with accessor logic. - -Expected impact: - -- Lower allocation rate on write-heavy workloads. -- Reduced memory footprint across all serializers and persistence engines. - -Compatibility risk: - -- Medium because callers may assume `Headers` is always a mutable non-null dictionary. -- This must be designed carefully to avoid subtle behavior changes. - -Validation requirements: - -- Unit tests for mutation semantics. -- Serialization tests for events with and without headers. -- Microbenchmarks around event creation. - -### 5. Reduce LINQ and Materialization in Hot Paths - -Affected areas: - -- `src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs` -- `src/NEventStore/OptimisticPipelineHook.cs` -- `src/NEventStore/OptimisticEventStore.cs` -- `src/NEventStore/Serialization/SerializationExtensions.cs` - -Current issue: - -- Several hot paths use LINQ plus immediate materialization (`ToArray`, `ToDictionary`, `OrderBy`, `SelectMany`) where straightforward loops would be cheaper. -- Some startup/configuration paths use `Any()` over enumerables that could be materialized once. - -Why this matters: - -- Individually these are moderate costs, but together they produce steady overhead in read and write loops. -- This is especially relevant for `netstandard2.0`, where modern JIT/runtime optimizations are less available than in `net8.0`. - -Suggested direction: - -- Replace LINQ on hot paths with explicit loops. -- Avoid `MemoryStream.ToArray()` when a buffer can be exposed or pre-sized safely. -- Replace repeated enumeration of hook lists with cached arrays where appropriate. - -Expected impact: - -- Moderate but widespread reductions in allocation pressure and CPU time. - -Compatibility risk: - -- Low if behavior remains the same. - -Validation requirements: - -- Benchmark before/after on the affected scenarios. -- Keep the code readable; do not trade away maintainability for micro-optimizations with negligible effect. -- Ensure the affected behavior is covered by tests, especially where loops replace LINQ and internal iteration logic changes. - -### 6. Improve Polling Client Efficiency - -Affected areas: - -- `src/NEventStore.PollingClient/AsyncPollingClient.cs` -- `src/NEventStore.PollingClient/PollingClient2.cs` -- `src/NEventStore.PollingClient/CommitSequencer.cs` - -Current issue: - -- The async polling client sleeps after every polling cycle even if progress was made. -- `StopAsync` uses a polling wait loop instead of awaiting the worker task directly. -- The synchronous polling client uses older thread/timer coordination primitives that are functional but not especially efficient. - -Why this matters: - -- Polling paths directly affect catch-up speed and idle overhead. -- These clients are often used for long-running projection/subscription workloads where wakeup patterns matter. - -Suggested direction: - -- Delay only when no commits were processed. -- Track the polling task and await it directly during shutdown. -- Review whether the synchronous client should keep its current design for compatibility while the async client becomes the primary optimized path. -- Add benchmarks or stress tests for idle polling, burst polling, and sustained catch-up. - -Expected impact: - -- Better catch-up latency. -- Lower idle CPU and timer churn. -- Cleaner shutdown behavior. - -Compatibility risk: - -- Low if public behavior is preserved. -- Medium if stop/retry sequencing semantics change. - -Validation requirements: - -- Integration tests around cancellation, stop, retry-on-hole, and backpressure. -- Benchmarks or load tests that measure: - - idle polling overhead - - catch-up throughput - - latency to process a new commit after it arrives - -### 7. Serializer and Byte-Handling Review - -Affected areas: - -- `src/NEventStore/Serialization/*` -- serializer packages under `src/NEventStore.Serialization.*` - -Current issue: - -- Some serializer utilities use `MemoryStream` and `ToArray`, which can force additional copies. -- The benchmark suite currently does not isolate serializer cost, so changes here are hard to evaluate. - -Why this matters: - -- In non-in-memory persistence engines, serialization can become a large part of end-to-end latency and allocation. - -Suggested direction: - -- Keep serializer interfaces stable. -- Add serializer-specific benchmarks for JSON, MessagePack, binary, and compression wrappers. -- Where safe, reduce intermediate copies and improve buffer sizing. -- Treat serializer modernization separately from the core event-store optimization work. - -Expected impact: - -- Moderate to high, depending on the persistence engine and serializer combination. - -Compatibility risk: - -- Medium because serializer changes can affect payload shape, metadata handling, and backward compatibility. - -Validation requirements: - -- Cross-version compatibility tests for stored payloads. -- Dedicated serializer benchmarks. -- Separate approval gate before changing serializer defaults. - -## Benchmark Suite Improvements - -The benchmark project needs improvement before it can serve as the main decision tool for the optimization effort. - -### Problems in the Current Benchmark Harness - -- It mixes benchmark overhead with library overhead. -- It only measures the in-memory persistence path. -- It does not separate stream materialization cost from raw persistence iteration cost cleanly enough. -- It does not include focused microbenchmarks for the specific hot methods identified above. - -### Benchmark Improvement Goals - -- Make benchmarks precise enough to support implementation tradeoffs. -- Separate allocation cost of the benchmark fixture from allocation cost of the library. -- Add coverage for low-level components and end-to-end flows. -- Keep benchmarks runnable on modern runtimes without changing the compatibility promise of the library itself. - -### Concrete Benchmark Work - -#### 1. Split the Current Persistence Benchmark into Smaller Scenarios - -Add separate benchmark classes for: - -- stream open/read -- commit attempt construction -- in-memory global checkpoint read -- in-memory stream revision read -- polling client catch-up -- polling client idle behavior -- serializer-only scenarios - -#### 2. Remove Benchmark-Side Noise - -Current benchmark code creates work that is not part of the library: - -- `Guid.NewGuid()` per commit -- `i.ToString()` per event - -Suggested improvement: - -- Pre-generate commit IDs and event payloads during setup. -- Reuse prebuilt event bodies where the benchmark goal is store overhead rather than object creation cost. -- If event payload creation is intentionally part of the scenario, isolate it in a separate benchmark and label it clearly. - -#### 3. Add Baselines and Parameterized Dimensions - -Useful dimensions: - -- commit count -- events per commit -- headers per event -- headers per commit -- empty vs populated stream -- sync vs async APIs -- with vs without optimistic pipeline hook -- serializer type - -Useful baselines: - -- raw in-memory commit enumeration -- stream open over same data -- direct serializer benchmark without store interaction - -#### 4. Add More Diagnosers and Exporters - -Useful additions: - -- memory diagnoser -- disassembly diagnoser for selected microbenchmarks -- markdown and csv exporters checked into artifacts -- explicit runtime jobs for comparison - -The benchmark project can target newer runtimes even if the core package remains compatible with older ones. - -#### 5. Separate Compatibility Targets from Benchmark Targets - -Recommended approach: - -- Keep the core package compatibility targets intact. -- Allow the benchmark project to target modern runtimes such as `net8.0` and `net9.0`. -- Optionally add benchmark runs against multiple runtimes to understand which gains come from code changes and which come from runtime improvements. - -#### 6. Add Regression Gates - -Once the suite is stable enough: - -- define a small set of representative benchmark scenarios -- track them in CI or scheduled runs -- compare against saved baselines before merging major internal changes - -This should be done after the benchmark suite is cleaned up, not before. - -## Modernization Strategy Without Losing Compatibility - -Modernization is still useful, but it should be layered. - -### Recommended Strategy - -#### 1. Preserve Current Public Targets - -- Keep `netstandard2.0` and `net462` for the core package. - -#### 2. Add Optional Modern Targets for Internal Fast Paths - -Where the payoff is clear, consider adding `net8.0` to selected packages: - -- core package -- polling client -- serializer packages that benefit from newer APIs - -This should be done only if: - -- the build/test matrix remains manageable -- package behavior stays consistent -- conditional code is kept contained and readable - -#### 3. Use Partial Classes or Conditional Compilation for Runtime-Specific Optimizations - -Examples of modern-only implementation choices: - -- pooled buffers -- runtime-specific collection helpers -- improved timers and async coordination -- lower-overhead serialization helpers - -The compatibility implementation should remain the default fallback. - -#### 4. Do Not Let Modernization Block the First Performance Wins - -The first wave should focus on structural improvements that help all supported runtimes: - -- better indexing -- fewer allocations -- fewer copies -- fewer linear scans - -These changes are valuable even without adding any new target framework. - -## Suggested Implementation Phases - -### Phase 0. Benchmark Cleanup and Baseline Definition - -Deliverables: - -- Refactored benchmark suite with smaller scenarios -- Reduced benchmark-generated noise -- Saved baseline reports for current implementation - -Why first: - -- This creates a reliable measurement framework before invasive changes begin. -- It also establishes the test matrix that later phases must use when validating behavior. - -### Phase 1. Low-Risk Internal Allocation Cuts - -Candidate work: - -- replace LINQ in hot paths -- pre-size collections -- optimize commit attempt building -- reduce copying where behavior is already clear - -Why here: - -- Low risk -- Immediate measurable wins -- Helps separate cheap improvements from structural ones -- Creates the first repeatable pattern of "change + benchmark + tests" for the rest of the program. - -### Phase 2. In-Memory Persistence Rework - -Candidate work: - -- global checkpoint index -- per-bucket/per-stream indexes -- dictionary-based heads and snapshots - -Why here: - -- Highest likely impact on existing benchmarks -- Also helps tests, examples, and polling scenarios - -### Phase 3. Stream Materialization Rework - -Candidate work: - -- replace linked lists with lists -- reduce commit/event copying -- review duplicate commit cache strategy - -Why here: - -- Large impact on `OpenStream` and `ReadFromStream` -- Likely one of the main contributors to the read gap vs raw event-store scans - -### Phase 4. Polling Client Optimization - -Candidate work: - -- more efficient delay/shutdown logic -- benchmark catch-up and idle overhead -- maintain behavior while improving operational efficiency - -### Phase 5. Optional Modern Fast Paths - -Candidate work: - -- add `net8.0` target where justified -- pool buffers -- runtime-specific optimizations - -Why last: - -- Should be additive, not foundational -- Easier once the compatibility implementation is already improved - -## Acceptance Criteria for the Future SPEC - -The formal SPEC should define target improvements for both allocations and latency. - -Recommended acceptance structure: - -- No public API break unless explicitly approved. -- Existing tests remain green across supported targets. -- Every code change is validated by tests, using existing tests when they are demonstrably sufficient and adding new tests when they are not. -- Benchmarks are reproducible and checked in with the SPEC. -- Each optimization phase must show improvement in at least one representative benchmark without unacceptable regressions elsewhere. -- Serialization compatibility must be preserved unless a separate migration plan is approved. - -Example benchmark success criteria: - -- measurable reduction in `WriteToStream` allocations at `10000` and `100000` commits -- measurable reduction in `ReadFromStream` allocation and latency at the same scales -- measurable reduction in polling catch-up latency -- no regression in correctness or duplicate/concurrency handling - -Example test-validation criteria: - -- the implementation plan names the existing tests that validate the changed behavior, or -- the implementation plan includes new tests in the same change set, with scope matching the affected behavior -- performance-only benchmark additions do not replace correctness tests - -## Open Questions for the SPEC - -These should be answered before implementation begins: - -- Which persistence implementations, beyond the in-memory engine, are important enough to benchmark in phase 1? -- Is duplicate commit detection guaranteed by every persistence implementation, or only by some of them? -- Is adding `net8.0` to the core package acceptable if `netstandard2.0` and `net462` remain supported? -- Should performance work optimize only the core package first, or should polling client improvements be included in the first milestone? -- Which benchmark scenarios should be treated as release-gating regressions? - -## Recommended First Milestone - -If the work needs to start with a narrow and high-value milestone, use this: - -- Clean up the benchmark suite. -- Rework the in-memory persistence indexing strategy. -- Replace `LinkedList` with `List` in `OptimisticEventStream`. -- Optimize commit attempt construction to reduce unnecessary allocations. - -This milestone is large enough to matter, but still focused on changes that are: - -- internal -- measurable -- broadly beneficial -- compatible with the current public surface - -## Summary - -The strongest current candidates are: - -- in-memory persistence indexing -- stream internal data structure changes -- commit construction allocation reduction -- polling client efficiency improvements -- benchmark harness cleanup - -The recommended approach is to improve the benchmark suite first, then implement internal low-risk changes, then move into structural read/write optimizations, and only after that add optional runtime-specific fast paths. - -That sequence gives NEventStore a credible path to substantially better performance while preserving the compatibility profile that existing users depend on. diff --git a/docs/Performance-Validation-Matrix.md b/docs/Performance-Validation-Matrix.md deleted file mode 100644 index 44da86fd4..000000000 --- a/docs/Performance-Validation-Matrix.md +++ /dev/null @@ -1,88 +0,0 @@ -# Performance Validation Matrix - -This document defines the correctness and measurement contract for performance foundation issue `#524` and the remaining implementation workstreams in umbrella issue `#533`. - -Use [Performance-Benchmarks.md](Performance-Benchmarks.md) for benchmark commands, scenario names, and artifact locations. - -Every performance change must satisfy both of these requirements: - -- The relevant correctness tests are run for the changed behavior. -- Before/after benchmark or measurement evidence is recorded from the focused benchmark suite introduced in `#523`. - -## Validation Anchors - -These existing suites are the baseline reference points for all later performance issues: - -- `src/NEventStore.Tests/OptimisticEventStoreTests.cs` and `src/NEventStore.Tests/OptimisticEventStoreTests.Async.cs`: stream open paths, snapshot-based open, and revision-range reads. -- `src/NEventStore.Tests/OptimisticEventStreamTests.cs` and `src/NEventStore.Tests/OptimisticEventStreamTests.Async.cs`: stream materialization, append and clear behavior, commit construction, duplicate commit identifiers, partial stream reads, and revision/commit sequence behavior. -- `src/NEventStore.Tests/OptimisticPipelineHookTests.cs`: commit sequence and stream revision invariants, plus delete/purge side effects on optimistic tracking. -- `src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.cs` and `src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Async.cs`: persistence ordering, checkpoint paging, duplicate detection, snapshot behavior, stream-to-snapshot behavior, and purge semantics. -- `src/NEventStore.Tests/Persistence/InMemory/InMemoryPersistenceTests.cs`: in-memory checkpoint range-boundary regressions. -- `src/NEventStore.Tests/Client/AsyncPollingClientTests.cs`, `src/NEventStore.Tests/Client/PollingClient2Tests.cs`, and `src/NEventStore.Tests/Client/PollingClientRxTests.cs`: polling dispatch, stop/retry/manual polling, and subscription semantics. -- `src/NEventStore.Persistence.AcceptanceTests/SerializationTests.cs` plus the fixture files under `src/NEventStore.Serialization.*.Tests/`: serializer round trips and wrapper compatibility. - -## Workstream Matrix - -### Issue #525: Reduce hot-path LINQ and commit construction allocations without behavior changes - -- Existing correctness suites: `OptimisticEventStreamTests(.Async)` for commit construction, header copying, append/clear behavior, duplicate commit identifiers, and revision/commit sequence updates; `OptimisticPipelineHookTests` for optimistic concurrency invariants. -- Mandatory additions: No new suite is required up front. If loop-based replacements change copy behavior, add sync and async unit tests that assert zero-count handling and copy independence for commit headers and committed events. -- Benchmark evidence: `CommitAttemptBenchmarks`, `StreamWriteBenchmarks`, and `StreamWriteLargeBenchmarks`. -- Completion gate: targeted stream tests remain green and at least one affected write-path benchmark shows a measurable improvement with no correctness regression. - -### Issue #526: Rework in-memory persistence reads to use direct indexes instead of full-store scans - -- Existing correctness suites: `PersistenceTests(.Async)` scenarios for reading from revision ranges, checkpoint paging, checkpoint-to-checkpoint paging, bucket-specific checkpoint reads, and checkpoint ordering across multiple buckets; `InMemoryPersistenceTests` for checkpoint range bounds. -- Mandatory additions: If direct indexes are introduced through new helper types or caches, add focused `InMemoryPersistenceTests` coverage for inclusive lower bounds, exclusive upper bounds, and empty-range results. If the refactor only replaces the internal implementation of the existing methods, the current acceptance coverage is the minimum required baseline. -- Benchmark evidence: `InMemoryReadBenchmarks`. -- Completion gate: ordering and range-boundary tests pass, and before/after results are recorded for global checkpoint reads, bucket checkpoint reads, and stream revision reads. - -### Issue #527: Replace scan-heavy stream-head and snapshot lookups in the in-memory engine - -- Existing correctness suites: `PersistenceTests(.Async)` scenarios for saving snapshots, retrieving the most recent prior snapshot, duplicate snapshot handling, streams-to-snapshot behavior after commits, bucket-specific snapshot isolation, purge store, and purge bucket; `OptimisticPipelineHookTests` for delete stream, purge bucket, and purge store tracking. -- Mandatory additions: Add in-memory-specific tests that directly verify stream-head and snapshot lookup structures stay correct after commit, snapshot add/update, delete stream, purge bucket, and purge store operations. The current acceptance tests validate behavior at the API boundary; this issue also needs direct regression coverage for index maintenance. -- Benchmark evidence: `StreamOpenBenchmarks` for stream-head lookup impact and `InMemoryReadBenchmarks` where supporting lookup structures affect visible read paths. If delete/purge changes are not measurable through the current benchmark set, record focused local measurements in the implementation issue. -- Completion gate: snapshot and purge semantics remain green under acceptance and unit tests, and measurable lookup-path changes are backed by benchmark or focused measurement data. - -### Issue #528: Replace linked-list event storage in OptimisticEventStream with allocation-efficient list storage - -- Existing correctness suites: `OptimisticEventStoreTests(.Async)` for opening empty streams, opening populated streams, and snapshot-based opens; `OptimisticEventStreamTests(.Async)` for partial reads, append one/many, clear behavior, commit behavior, collection mutation guards, duplicate identifiers, and the `issue_420` regression scenarios. -- Mandatory additions: No new suite is required if the public `ICollection` behavior remains unchanged. If the storage swap affects collection exposure semantics, add sync and async tests that explicitly assert committed/uncommitted event order is preserved and that external callers still cannot mutate the exposed committed event collections. -- Benchmark evidence: `StreamOpenBenchmarks`, `StreamWriteBenchmarks`, and `StreamWriteLargeBenchmarks`. -- Completion gate: open/read and write benchmarks show a measurable gain, and stream revision, commit sequence, partial-stream, and collection-behavior tests remain unchanged. - -### Issue #529: Audit duplicate commit-id enforcement before changing stream-level identifier tracking - -- Existing correctness suites: `PersistenceTests(.Async)` scenarios for persisting the same commit twice and reusing a commit ID on the same stream; `OptimisticEventStreamTests(.Async)` scenarios for committing with an identifier that was previously read. -- Audit note: [Performance-DuplicateCommitId-Audit.md](C:/Work/NEventStore/NEventStore/docs/Performance-DuplicateCommitId-Audit.md) records the provider evidence and recommends removing `_identifiers` in a follow-up change, not in the audit issue itself. -- Mandatory additions: No implementation change is allowed until an audit note documents duplicate-commit-ID guarantees for the in-repo in-memory persistence engine and any supported external providers. If the chosen direction changes `_identifiers` behavior, add sync and async tests for duplicate commit IDs after reopening a stream and after partial-stream reads. -- Benchmark evidence: none for the audit-only issue. Any later implementation should reuse the write benchmarks from `#525`/`#528`. -- Completion gate: provider evidence is documented, the follow-up direction is explicit, and the required follow-up tests are named before any code change lands. - -### Issue #530: Review lazy header allocation for EventMessage without breaking compatibility - -- Existing correctness suites: `PersistenceTests(.Async)` for header persistence, `OptimisticEventStreamTests(.Async)` for commit-header copy behavior, and `SerializationTests` for header dictionary round trips. -- Mandatory additions: Add explicit compatibility tests before changing `EventMessage.Headers`. At minimum, assert that a new `EventMessage` exposes a non-null writable `Headers` dictionary, header mutations survive commit construction, and empty/populated headers round-trip through every affected serializer or wrapper. -- Benchmark evidence: `CommitAttemptBenchmarks`; if serializer behavior is touched, also use `SerializerRoundTripBenchmarks`. -- Completion gate: compatibility-sensitive tests are added first, the semantics decision is explicit, and any approved implementation is benchmark-backed. - -### Issue #531: Improve polling client efficiency for catch-up, idle waits, and shutdown - -- Existing correctness suites: `AsyncPollingClientTests` for dispatch, stop behavior, and manual polling; `PollingClient2Tests` for stop, retry, retry-then-move-next, and manual polling; `PollingClientRxTests` for subscription and bucket-filtering semantics. -- Mandatory additions: Add tests for cancellation-token shutdown, worker completion during dispose/stop, and the no-extra-delay-after-progress behavior in the polling implementation being changed. The current suites do not explicitly prove cancellation and shutdown completion semantics. -- Benchmark evidence: `AsyncPollingBenchmarks`. If shutdown improvements cannot be demonstrated through the microbenchmark harness, record a focused stress measurement in the implementation issue. -- Completion gate: polling semantics remain correct under tests, and idle or catch-up measurements show a measurable operational improvement. - -### Issue #532: Add serializer benchmarks and review byte-handling copies in serialization utilities - -- Existing correctness suites: `SerializationTests` for object, event-message, header, and snapshot payload round trips, plus the serializer-specific fixture files under `src/NEventStore.Serialization.*.Tests/`. -- Mandatory additions: If copy-heavy utilities or wrappers change, add focused tests in each affected serializer project for large payload round trips and wrapper equivalence. The current benchmark suite only establishes JSON and gzip-wrapped JSON baselines, so extend serializer benchmarks to cover the serializer or wrapper being optimized before claiming a performance win. -- Benchmark evidence: `SerializerRoundTripBenchmarks`, expanded as needed for the serializer/wrapper under change. -- Completion gate: compatibility-sensitive serializer tests remain green, benchmark baselines exist for the changed serializer path, and before/after numbers are recorded with the implementation. - -### Issue #537: Establish benchmark baselines and regression gate policy for modern fast paths - -- Existing correctness suites: no functional behavior changes are expected. Keep the full Release test suite as the regression check for the documentation and governance update. -- Mandatory additions: Add a checked-in Phase 5 regression gate policy and a curated baseline artifact under `docs/performance-baselines/`; raw BenchmarkDotNet output remains under ignored `artifacts/benchmarks/results/`. -- Benchmark evidence: full joined benchmark run via `dotnet .\src\NEventStore.Benchmark\bin\Release\net8.0\NEventStore.Benchmark.dll --filter * --join`. -- Completion gate: the policy lists gate scenarios, thresholds, review reporting format, and exception workflow, and the checked-in baseline records the source report names and representative gate rows. diff --git a/docs/Testing.md b/docs/Testing.md deleted file mode 100644 index 1613ea91c..000000000 --- a/docs/Testing.md +++ /dev/null @@ -1,23 +0,0 @@ -# Testing And Test Frameworks in NEventStore - -While upgrading the solution to support dotnet core, we also tried to migrate the tests to other test frameworks -(because not all of them supported dotnet core correctly when the migration job started). - -Several trial and errors were made, but in the end we were able to implement the tests using all the 3 major testing frameworks available in the dotnet world: - -- XUnit -- NUnit -- MSTest - -We had to write 3 version of the `SpecificationBase` class and adapt the testing attributes to each framework. - -I you inspect the code you'll see a lot of `#if NUNIT` (and the like) lines of code. - -The actual implementation compiles all the projects to use NUnit. - -You can change the behavior following these steps: - -- go through all the .csproj files and change the compilation constant from NUNIT to XUNIT or MSTEST. -- in the assemblies that contain tests you need to reference the correct Test Framework assemblies and TestAdapter for the framework you are going to use. - -Having more than one test runner might not be a good idea because some CI tools (like Appveyor) might autodetect them and execute the tests for a framework you are not using, and it will surely endup with failures and errors of your build. \ No newline at end of file diff --git a/docs/performance-baselines/phase5-modern-fastpaths-2026-04-20.md b/docs/performance-baselines/phase5-modern-fastpaths-2026-04-20.md deleted file mode 100644 index 7f69d9174..000000000 --- a/docs/performance-baselines/phase5-modern-fastpaths-2026-04-20.md +++ /dev/null @@ -1,46 +0,0 @@ -# Phase 5 Modern Fast Paths Baseline - 2026-04-20 - -This is the checked-in seed baseline for issue `#537`. It summarizes the representative gate rows from a full joined BenchmarkDotNet run after the Phase 5 modern target and serializer work through `#536`. - -## Source Run - -- Command: `dotnet .\src\NEventStore.Benchmark\bin\Release\net8.0\NEventStore.Benchmark.dll --filter * --join` -- Executed benchmarks: 66 -- Total benchmark time: 00:10:29 -- Raw Markdown: `artifacts/benchmarks/results/BenchmarkRun-joined-2026-04-20-18-26-00-report-github.md` -- Raw CSV: `artifacts/benchmarks/results/BenchmarkRun-joined-2026-04-20-18-26-00-report.csv` -- Raw HTML: `artifacts/benchmarks/results/BenchmarkRun-joined-2026-04-20-18-26-00-report.html` - -## Environment - -- BenchmarkDotNet: 0.15.8 -- OS: Windows 11 10.0.26200.8246 -- CPU: 13th Gen Intel Core i7-13700K, 24 logical cores, 16 physical cores -- .NET SDK: 10.0.300-preview.0.26177.108 -- Runtime: .NET 8.0.26, X64 RyuJIT x86-64-v3 - -## Gate Rows - -| Type | Method | Parameters | Mean | Allocated | -| --- | --- | --- | ---: | ---: | -| `CommitAttemptBenchmarks` | `ConstructCommitAttempt` | `HeaderCount=5`, `EventsPerCommit=100` | 803.0 ns | 2408 B | -| `SerializerRoundTripBenchmarks` | `BinaryRoundTrip` | `EventCount=100`, `PayloadSizeBytes=4096` | 2,039,007.4 ns | 3328031 B | -| `SerializerRoundTripBenchmarks` | `BsonRoundTrip` | `EventCount=100`, `PayloadSizeBytes=4096` | 1,607,547.3 ns | 2973492 B | -| `SerializerRoundTripBenchmarks` | `JsonRoundTrip` | `EventCount=100`, `PayloadSizeBytes=4096` | 821,249.1 ns | 1722646 B | -| `SerializerRoundTripBenchmarks` | `GzipJsonRoundTrip` | `EventCount=100`, `PayloadSizeBytes=4096` | 1,727,243.3 ns | 1156481 B | -| `SerializerRoundTripBenchmarks` | `GzipBinaryRoundTrip` | `EventCount=100`, `PayloadSizeBytes=4096` | 80,861,367.9 ns | 2734469 B | -| `SerializerRoundTripBenchmarks` | `MsgPackRoundTrip` | `EventCount=100`, `PayloadSizeBytes=4096` | 308,341.7 ns | 1941400 B | -| `SerializerRoundTripBenchmarks` | `RijndaelBinaryRoundTrip` | `EventCount=100`, `PayloadSizeBytes=4096` | 2,471,770.7 ns | 3343284 B | -| `AsyncPollingBenchmarks` | `IdlePollAsync` | `CommitCount=1000` | 167.7 ns | 440 B | -| `AsyncPollingBenchmarks` | `CatchUpPollAsync` | `CommitCount=1000` | 6,369.9 ns | 8496 B | -| `StreamOpenBenchmarks` | `OpenPopulatedStream` | `CommitCount=100000` | 7,695,744.4 ns | 5849320 B | -| `StreamOpenBenchmarks` | `OpenPopulatedStreamAsync` | `CommitCount=100000` | 7,671,966.7 ns | 6898512 B | -| `StreamWriteLargeBenchmarks` | `AppendSingleEventCommits` | `CommitCount=100000` | 404,506,038.2 ns | 213625295 B | -| `StreamWriteLargeBenchmarks` | `AppendSingleEventCommitsAsync` | `CommitCount=100000` | 356,413,684.0 ns | 242424458 B | -| `InMemoryReadBenchmarks` | `ReadGlobalCheckpointRange` | `CommitCount=100000` | 762,465.3 ns | 400052 B | -| `InMemoryReadBenchmarks` | `ReadBucketCheckpointRange` | `CommitCount=100000` | 843,143.7 ns | 400052 B | -| `InMemoryReadBenchmarks` | `ReadStreamRevisionRange` | `CommitCount=100000` | 4,795.3 ns | 12640 B | - -## Notes - -BenchmarkDotNet reported minimum-iteration warnings for several stream-open and write scenarios. Treat those rows as directional gate signals, not hard blockers, until repeated scheduled runs establish stable variance. diff --git a/icon.png b/icon.png deleted file mode 100644 index c6c977dab..000000000 Binary files a/icon.png and /dev/null differ diff --git a/images/bg_hr.png b/images/bg_hr.png new file mode 100644 index 000000000..7973bd698 Binary files /dev/null and b/images/bg_hr.png differ diff --git a/images/blacktocat.png b/images/blacktocat.png new file mode 100644 index 000000000..6e264fe57 Binary files /dev/null and b/images/blacktocat.png differ diff --git a/images/icon_download.png b/images/icon_download.png new file mode 100644 index 000000000..a2a287f64 Binary files /dev/null and b/images/icon_download.png differ diff --git a/images/logo_dotcover_small.gif b/images/logo_dotcover_small.gif new file mode 100644 index 000000000..caa386971 Binary files /dev/null and b/images/logo_dotcover_small.gif differ diff --git a/images/logo_dottrace_small.gif b/images/logo_dottrace_small.gif new file mode 100644 index 000000000..cfe88489a Binary files /dev/null and b/images/logo_dottrace_small.gif differ diff --git a/images/logo_resharper_small.gif b/images/logo_resharper_small.gif new file mode 100644 index 000000000..739dcb4d5 Binary files /dev/null and b/images/logo_resharper_small.gif differ diff --git a/images/logo_teamcity.gif b/images/logo_teamcity.gif new file mode 100644 index 000000000..79a362603 Binary files /dev/null and b/images/logo_teamcity.gif differ diff --git a/images/logo_teamcity_small.gif b/images/logo_teamcity_small.gif new file mode 100644 index 000000000..ef7ddbd6f Binary files /dev/null and b/images/logo_teamcity_small.gif differ diff --git a/images/sprite_download.png b/images/sprite_download.png new file mode 100644 index 000000000..f2babd575 Binary files /dev/null and b/images/sprite_download.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..5fcc94253 --- /dev/null +++ b/index.html @@ -0,0 +1,89 @@ + + + + + + + + + + + NEventStore + + + + + +
+
+ View on GitHub + +

NEventStore

+

A persistence agnostic Event Store for .NET

+
+
+ + +
+ +
+
+ Developed with: + + + + +
+

Overview

+

+ The NEventStore is a persistence library used to abstract different storage implementations when using event + sourcing as storage mechanism. It targeted at DDD (Domain Driven Design) and CQRS (Command Query Responsibility + Segregation) based applications. It is not intended to be a general stream store. +

+

Start using NEventStore

+

See the Quick Start and browse the Documentation.

+

Announcements

+

Follow us on twitter: @NEventStore, @randompunter, @kblooie, + @andreabalducci, @A_Giorgetti +

Need Help? Have a Question?

+

+ Ask your question on Google group or post to the + Issue Tracker. +

+

Development

+

Code is on GitHub

+ +

+ Package feeds: +

+

+
+ +
+ + + + + + \ No newline at end of file diff --git a/javascripts/main.js b/javascripts/main.js new file mode 100644 index 000000000..d8135d37b --- /dev/null +++ b/javascripts/main.js @@ -0,0 +1 @@ +console.log('This would be the main JS file.'); diff --git a/license.txt b/license.txt deleted file mode 100644 index 6a5550266..000000000 --- a/license.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2013 Jonathan Oliver, Jonathan Matheus, Damian Hickey and contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/params.json b/params.json new file mode 100644 index 000000000..e5862fa53 --- /dev/null +++ b/params.json @@ -0,0 +1 @@ +{"name":"NEventStore","tagline":"A persistence agnostic Event Store for .NET","body":"### Welcome to GitHub Pages.\r\nThis automatic page generator is the easiest way to create beautiful pages for all of your projects. Author your page content here using GitHub Flavored Markdown, select a template crafted by a designer, and publish. After your page is generated, you can check out the new branch:\r\n\r\n```\r\n$ cd your_repo_root/repo_name\r\n$ git fetch origin\r\n$ git checkout gh-pages\r\n```\r\n\r\nIf you're using the GitHub for Mac, simply sync your repository and you'll see the new branch.\r\n\r\n### Designer Templates\r\nWe've crafted some handsome templates for you to use. Go ahead and continue to layouts to browse through them. You can easily go back to edit your page before publishing. After publishing your page, you can revisit the page generator and switch to another theme. Your Page content will be preserved if it remained markdown format.\r\n\r\n### Rather Drive Stick?\r\nIf you prefer to not use the automatic generator, push a branch named `gh-pages` to your repository to create a page manually. In addition to supporting regular HTML content, GitHub Pages support Jekyll, a simple, blog aware static site generator written by our own Tom Preston-Werner. Jekyll makes it easy to create site-wide headers and footers without having to copy them across every page. It also offers intelligent blog support and other advanced templating features.\r\n\r\n### Authors and Contributors\r\nYou can @mention a GitHub username to generate a link to their profile. The resulting `` element will link to the contributor's GitHub Profile. For example: In 2007, Chris Wanstrath (@defunkt), PJ Hyett (@pjhyett), and Tom Preston-Werner (@mojombo) founded GitHub.\r\n\r\n### Support or Contact\r\nHaving trouble with Pages? Check out the documentation at http://help.github.com/pages or contact support@github.com and we’ll help you sort it out.","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."} \ No newline at end of file diff --git a/src/.editorconfig b/src/.editorconfig deleted file mode 100644 index 147f33782..000000000 --- a/src/.editorconfig +++ /dev/null @@ -1,39 +0,0 @@ -[*] -end_of_line = crlf -indent_style = space -indent_size = 4 - -[*.xml] -indent_style = space - -[*.cs] -csharp_new_line_before_open_brace = all - -# RCS1229: Use async/await when necessary. -dotnet_diagnostic.RCS1229.severity = error - -# IDE0290: Use primary constructor -csharp_style_prefer_primary_constructors = false - -# IDE0028: Simplify collection initialization -dotnet_style_collection_initializer = false - -# IDE0305: Simplify collection initialization -dotnet_diagnostic.IDE0305.severity = none - -# Logging Warnings: temporary disabled, enable them again later on - -# CA1848: Use the LoggerMessage delegates -dotnet_diagnostic.CA1848.severity = none - -# CA2254: Template should be a static expression -dotnet_diagnostic.CA2254.severity = none - -# CA1711: Identifiers should not have incorrect suffix -dotnet_diagnostic.CA1711.severity = none - -# CA1510: Use ArgumentNullException throw helper -dotnet_diagnostic.CA1510.severity = none - -# CA1513: Use ObjectDisposedException throw helper -dotnet_diagnostic.CA1513.severity = none diff --git a/src/AssemblyInfo.cs b/src/AssemblyInfo.cs deleted file mode 100644 index a2d615453..000000000 --- a/src/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -[assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/CustomDictionary.xml b/src/CustomDictionary.xml deleted file mode 100644 index a13c808b0..000000000 --- a/src/CustomDictionary.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - wireup - bson - untyped - param - shard - sharding - sqlite - postgre - rds - nano - smallint - tinyint - datetime - dbo - uniqueidentifier - varbinary - xtype - bigint - bytea - uuid - precommit - changeset - nonpositive - sysobjects - gzip - precommit - - - \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props deleted file mode 100644 index 20046dba9..000000000 --- a/src/Directory.Build.props +++ /dev/null @@ -1,12 +0,0 @@ - - - 14.0 - enable - enable - - net8.0;net9.0;net10.0 - - diff --git a/src/GlobalAssemblyInfo.cs b/src/GlobalAssemblyInfo.cs deleted file mode 100644 index 2b7d4963a..000000000 --- a/src/GlobalAssemblyInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Jonathan Oliver. All rights reserved. -// -//----------------------------------------------------------------------- - -using System; -using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyCompany("NEventStore")] -[assembly: AssemblyProduct("NEventStore")] -[assembly: AssemblyCopyright("Copyright © Jonathan Oliver, Jonathan Mathius, Damian Hickey and Contributors 2011")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] -[assembly: CLSCompliant(false)] -[assembly: NeutralResourcesLanguage("en-US")] -[assembly: InternalsVisibleTo("NEventStore.Tests")] -[assembly: InternalsVisibleTo("NEventStore.Core.Tests")] diff --git a/src/GlobalSuppressions.cs b/src/GlobalSuppressions.cs deleted file mode 100644 index fe8e858ee..000000000 --- a/src/GlobalSuppressions.cs +++ /dev/null @@ -1,5 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Jonathan Oliver. All rights reserved. -// -//----------------------------------------------------------------------- \ No newline at end of file diff --git a/src/NEventStore.Benchmark/Benchmarks/AsyncPollingBenchmarks.cs b/src/NEventStore.Benchmark/Benchmarks/AsyncPollingBenchmarks.cs deleted file mode 100644 index 0bedef429..000000000 --- a/src/NEventStore.Benchmark/Benchmarks/AsyncPollingBenchmarks.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Reflection; -using BenchmarkDotNet.Attributes; -using NEventStore.Benchmark.Support; -using NEventStore.Persistence; -using NEventStore.Persistence.InMemory; -using NEventStore.PollingClient; - -namespace NEventStore.Benchmark.Benchmarks -{ - [Config(typeof(AllowNonOptimized))] - [SimpleJob(launchCount: 1, warmupCount: 3, iterationCount: 5)] - [MemoryDiagnoser] - [MeanColumn, StdErrorColumn, StdDevColumn, MinColumn, MaxColumn, IterationsColumn] - public class AsyncPollingBenchmarks - { - private static readonly MethodInfo ConfigurePollingClientMethod = - typeof(AsyncPollingClient).GetMethod("ConfigurePollingClient", BindingFlags.Instance | BindingFlags.NonPublic) - ?? throw new InvalidOperationException("Unable to locate AsyncPollingClient.ConfigurePollingClient"); - - private InMemoryPersistenceEngine? _idleEngine; - private InMemoryPersistenceEngine? _catchUpEngine; - - [Params(1000)] - public int CommitCount { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - _idleEngine = new InMemoryPersistenceEngine(); - _idleEngine.Initialize(); - - _catchUpEngine = new InMemoryPersistenceEngine(); - _catchUpEngine.Initialize(); - - for (var i = 0; i < CommitCount; i++) - { - _catchUpEngine.Commit(new CommitAttempt( - Bucket.Default, - $"polling-stream-{i % 32:D2}", - (i / 32) + 1, - Guid.NewGuid(), - (i / 32) + 1, - DateTime.UtcNow, - null, - [new EventMessage { Body = new SomeDomainEvent { Value = i.ToString() } }])); - } - } - - [GlobalCleanup] - public void GlobalCleanup() - { - _idleEngine?.Dispose(); - _catchUpEngine?.Dispose(); - _idleEngine = null; - _catchUpEngine = null; - } - - [Benchmark] - public async Task IdlePollAsync() - { - using var client = CreateClient(IdleEngine); - await client.PollAsync(CancellationToken.None).ConfigureAwait(false); - } - - [Benchmark] - public async Task CatchUpPollAsync() - { - using var client = CreateClient(CatchUpEngine); - await client.PollAsync(CancellationToken.None).ConfigureAwait(false); - } - - private AsyncPollingClient CreateClient(IPersistStreams persistStreams) - { - var client = new AsyncPollingClient(persistStreams, new NoOpCommitObserver()); - ConfigurePollingClientMethod.Invoke(client, [0L, null]); - return client; - } - - private InMemoryPersistenceEngine IdleEngine => _idleEngine ?? throw new InvalidOperationException("Idle engine has not been initialized."); - - private InMemoryPersistenceEngine CatchUpEngine => _catchUpEngine ?? throw new InvalidOperationException("Catch-up engine has not been initialized."); - - private sealed class NoOpCommitObserver : IAsyncObserver - { - public Task OnCompletedAsync(CancellationToken cancellationToken = default) - { - return Task.CompletedTask; - } - - public Task OnErrorAsync(Exception ex, CancellationToken cancellationToken = default) - { - throw ex; - } - - public Task OnNextAsync(ICommit value, CancellationToken cancellationToken = default) - { - return Task.FromResult(true); - } - } - } -} diff --git a/src/NEventStore.Benchmark/Benchmarks/CommitAttemptBenchmarks.cs b/src/NEventStore.Benchmark/Benchmarks/CommitAttemptBenchmarks.cs deleted file mode 100644 index 370817476..000000000 --- a/src/NEventStore.Benchmark/Benchmarks/CommitAttemptBenchmarks.cs +++ /dev/null @@ -1,58 +0,0 @@ -using BenchmarkDotNet.Attributes; -using NEventStore.Benchmark.Support; - -namespace NEventStore.Benchmark.Benchmarks -{ - [Config(typeof(AllowNonOptimized))] - [SimpleJob(launchCount: 1, warmupCount: 3, iterationCount: 5)] - [MemoryDiagnoser] - [MeanColumn, StdErrorColumn, StdDevColumn, MinColumn, MaxColumn, IterationsColumn] - public class CommitAttemptBenchmarks - { - private static readonly DateTime CommitStamp = new(2026, 04, 16, 12, 0, 0, DateTimeKind.Utc); - private static readonly Guid CommitId = new("e63c1cc7-7fe7-47ee-8a9d-c62093e6c755"); - private KeyValuePair[] _headerEntries = []; - private EventMessage[] _events = []; - - [Params(0, 5)] - public int HeaderCount { get; set; } - - [Params(1, 10, 100)] - public int EventsPerCommit { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - _headerEntries = Enumerable.Range(0, HeaderCount) - .Select(i => new KeyValuePair($"header-{i}", $"value-{i}")) - .ToArray(); - - _events = Enumerable.Range(0, EventsPerCommit) - .Select(i => new EventMessage { Body = new SomeDomainEvent { Value = i.ToString() } }) - .ToArray(); - } - - [Benchmark] - public CommitAttempt ConstructCommitAttempt() - { - var headers = new Dictionary(HeaderCount); - foreach (var header in _headerEntries) - { - headers.Add(header.Key, header.Value); - } - - var events = new EventMessage[_events.Length]; - Array.Copy(_events, events, _events.Length); - - return new CommitAttempt( - Bucket.Default, - "benchmark-stream", - events.Length, - CommitId, - 1, - CommitStamp, - headers, - events); - } - } -} diff --git a/src/NEventStore.Benchmark/Benchmarks/InMemoryReadBenchmarks.cs b/src/NEventStore.Benchmark/Benchmarks/InMemoryReadBenchmarks.cs deleted file mode 100644 index 5c9fcfeca..000000000 --- a/src/NEventStore.Benchmark/Benchmarks/InMemoryReadBenchmarks.cs +++ /dev/null @@ -1,106 +0,0 @@ -using BenchmarkDotNet.Attributes; -using NEventStore.Benchmark.Support; -using NEventStore.Persistence.InMemory; - -namespace NEventStore.Benchmark.Benchmarks -{ - [Config(typeof(AllowNonOptimized))] - [SimpleJob(launchCount: 1, warmupCount: 3, iterationCount: 5)] - [MemoryDiagnoser] - [MeanColumn, StdErrorColumn, StdDevColumn, MinColumn, MaxColumn, IterationsColumn] - public class InMemoryReadBenchmarks - { - private const int StreamCount = 64; - private readonly string[] _streamIds = Enumerable.Range(0, StreamCount).Select(i => $"stream-{i:D2}").ToArray(); - private InMemoryPersistenceEngine? _engine; - private long _midpointCheckpoint; - private string? _selectedStreamId; - private int _selectedMinRevision; - - [Params(10000, 100000)] - public int CommitCount { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - var streamRevisions = new int[StreamCount]; - var streamCommitSequences = new int[StreamCount]; - var midpointIndex = CommitCount / 2; - - for (var i = 0; i < CommitCount; i++) - { - var streamIndex = i % StreamCount; - streamRevisions[streamIndex]++; - streamCommitSequences[streamIndex]++; - - var commit = _engine.Commit(new CommitAttempt( - Bucket.Default, - _streamIds[streamIndex], - streamRevisions[streamIndex], - Guid.NewGuid(), - streamCommitSequences[streamIndex], - DateTime.UtcNow, - null, - [new EventMessage { Body = new SomeDomainEvent { Value = i.ToString() } }])); - - if (i == midpointIndex) - { - _midpointCheckpoint = commit!.CheckpointToken; - } - } - - _selectedStreamId = _streamIds[0]; - _selectedMinRevision = Math.Max(1, (CommitCount / StreamCount) / 2); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - _engine?.Dispose(); - _engine = null; - } - - [Benchmark] - public int ReadGlobalCheckpointRange() - { - var totalEvents = 0; - foreach (var commit in Engine.GetFrom(_midpointCheckpoint)) - { - totalEvents += commit.Events.Count; - } - - return totalEvents; - } - - [Benchmark] - public int ReadBucketCheckpointRange() - { - var totalEvents = 0; - foreach (var commit in Engine.GetFrom(Bucket.Default, _midpointCheckpoint)) - { - totalEvents += commit.Events.Count; - } - - return totalEvents; - } - - [Benchmark] - public int ReadStreamRevisionRange() - { - var totalEvents = 0; - foreach (var commit in Engine.GetFrom(Bucket.Default, SelectedStreamId, _selectedMinRevision, int.MaxValue)) - { - totalEvents += commit.Events.Count; - } - - return totalEvents; - } - - private InMemoryPersistenceEngine Engine => _engine ?? throw new InvalidOperationException("Benchmark engine has not been initialized."); - - private string SelectedStreamId => _selectedStreamId ?? throw new InvalidOperationException("Benchmark stream has not been initialized."); - } -} diff --git a/src/NEventStore.Benchmark/Benchmarks/SerializerRoundTripBenchmarks.cs b/src/NEventStore.Benchmark/Benchmarks/SerializerRoundTripBenchmarks.cs deleted file mode 100644 index 6a9bb2fb0..000000000 --- a/src/NEventStore.Benchmark/Benchmarks/SerializerRoundTripBenchmarks.cs +++ /dev/null @@ -1,144 +0,0 @@ -using BenchmarkDotNet.Attributes; -using NEventStore.Benchmark.Support; -using NEventStore.Serialization; -using NEventStore.Serialization.Binary; -using NEventStore.Serialization.Bson; -using NEventStore.Serialization.Json; -using NEventStore.Serialization.MsgPack; - -namespace NEventStore.Benchmark.Benchmarks -{ - [Config(typeof(AllowNonOptimized))] - [SimpleJob(launchCount: 1, warmupCount: 3, iterationCount: 5)] - [MemoryDiagnoser] - [MeanColumn, StdErrorColumn, StdDevColumn, MinColumn, MaxColumn, IterationsColumn] - public class SerializerRoundTripBenchmarks - { - private static readonly byte[] EncryptionKey = - [ - 0x1, 0x2, 0x3, 0x4, - 0x5, 0x6, 0x7, 0x8, - 0x9, 0xa, 0xb, 0xc, - 0xd, 0xe, 0xf, 0x0 - ]; - - private ISerialize? _binarySerializer; - private ISerialize? _bsonSerializer; - private ISerialize? _jsonSerializer; - private ISerialize? _gzipJsonSerializer; - private ISerialize? _gzipBinarySerializer; - private ISerialize? _msgPackSerializer; - private ISerialize? _rijndaelBinarySerializer; - private List? _payload; - - [Params(10, 100)] - public int EventCount { get; set; } - - [Params(64, 4096)] - public int PayloadSizeBytes { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - // BinarySerializer: added EnableUnsafeBinaryFormatterSerialization in .csproj - // AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); - - _binarySerializer = CreateBinarySerializer(); - _bsonSerializer = new BsonSerializer(); - _jsonSerializer = new JsonSerializer(null); - _gzipJsonSerializer = new GzipSerializer(new JsonSerializer(null)); - _gzipBinarySerializer = new GzipSerializer(CreateBinarySerializer()); - _msgPackSerializer = new MsgPackSerializer(); - _rijndaelBinarySerializer = new RijndaelSerializer(CreateBinarySerializer(), EncryptionKey); - // Keep the matrix deliberately small but representative: plain serializers measure their - // own object/byte costs, while the gzip and Rijndael wrappers expose extra buffering and - // copy work on both small metadata-heavy payloads and larger body-heavy payloads. - _payload = Enumerable.Range(0, EventCount) - .Select(i => - { - var message = new EventMessage - { - Body = new SomeDomainEvent - { - Value = $"{i:D4}-{new string((char)('a' + (i % 26)), PayloadSizeBytes)}" - } - }; - message.Headers["index"] = i; - return message; - }) - .ToList(); - } - - [Benchmark] - public List? BinaryRoundTrip() - { - var bytes = BinarySerializer.Serialize(Payload); - return BinarySerializer.Deserialize>(bytes); - } - - [Benchmark] - public List? BsonRoundTrip() - { - var bytes = BsonSerializer.Serialize(Payload); - return BsonSerializer.Deserialize>(bytes); - } - - [Benchmark] - public List? JsonRoundTrip() - { - var bytes = JsonSerializer.Serialize(Payload); - return JsonSerializer.Deserialize>(bytes); - } - - [Benchmark] - public List? GzipJsonRoundTrip() - { - var bytes = GzipJsonSerializer.Serialize(Payload); - return GzipJsonSerializer.Deserialize>(bytes); - } - - [Benchmark] - public List? GzipBinaryRoundTrip() - { - var bytes = GzipBinarySerializer.Serialize(Payload); - return GzipBinarySerializer.Deserialize>(bytes); - } - - [Benchmark] - public List? MsgPackRoundTrip() - { - var bytes = MsgPackSerializer.Serialize(Payload); - return MsgPackSerializer.Deserialize>(bytes); - } - - [Benchmark] - public List? RijndaelBinaryRoundTrip() - { - var bytes = RijndaelBinarySerializer.Serialize(Payload); - return RijndaelBinarySerializer.Deserialize>(bytes); - } - -#pragma warning disable CS0618 - private static ISerialize CreateBinarySerializer() - { - return new BinarySerializer(); - } -#pragma warning restore CS0618 - - private ISerialize BinarySerializer => _binarySerializer ?? throw new InvalidOperationException("Serializer has not been initialized."); - - private ISerialize BsonSerializer => _bsonSerializer ?? throw new InvalidOperationException("Serializer has not been initialized."); - - private ISerialize JsonSerializer => _jsonSerializer ?? throw new InvalidOperationException("Serializer has not been initialized."); - - private ISerialize GzipJsonSerializer => _gzipJsonSerializer ?? throw new InvalidOperationException("Serializer has not been initialized."); - - private ISerialize GzipBinarySerializer => _gzipBinarySerializer ?? throw new InvalidOperationException("Serializer has not been initialized."); - - private ISerialize MsgPackSerializer => _msgPackSerializer ?? throw new InvalidOperationException("Serializer has not been initialized."); - - private ISerialize RijndaelBinarySerializer => _rijndaelBinarySerializer ?? throw new InvalidOperationException("Serializer has not been initialized."); - - private List Payload => _payload ?? throw new InvalidOperationException("Payload has not been initialized."); - } -} diff --git a/src/NEventStore.Benchmark/Benchmarks/StreamOpenBenchmarks.cs b/src/NEventStore.Benchmark/Benchmarks/StreamOpenBenchmarks.cs deleted file mode 100644 index f39d0d3ca..000000000 --- a/src/NEventStore.Benchmark/Benchmarks/StreamOpenBenchmarks.cs +++ /dev/null @@ -1,96 +0,0 @@ -using BenchmarkDotNet.Attributes; -using NEventStore.Benchmark.Support; - -namespace NEventStore.Benchmark.Benchmarks -{ - [Config(typeof(AllowNonOptimized))] - [SimpleJob(launchCount: 3, warmupCount: 3, iterationCount: 3)] - [MemoryDiagnoser] - [MeanColumn, StdErrorColumn, StdDevColumn, MinColumn, MaxColumn, IterationsColumn] - public class StreamOpenBenchmarks - { - private static readonly Guid StreamId = new("7f4f4d6c-0b32-4f56-a7e2-bf6d4f5f60ab"); - private IStoreEvents? _eventStore; - private PreGeneratedStreamData? _data; - - [Params(100, 1000, 10000, 100000)] - public int CommitCount { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - _data = PreGeneratedStreamData.Create(CommitCount); - } - - [IterationSetup(Target = nameof(OpenEmptyStream))] - public void SetupEmptyStream() - { - _eventStore = EventStoreHelpers.WireupEventStore(); - } - - [IterationSetup(Target = nameof(OpenEmptyStreamAsync))] - public void SetupEmptyStreamAsync() - { - _eventStore = EventStoreHelpers.WireupEventStore(); - } - - [IterationSetup(Target = nameof(OpenPopulatedStream))] - public void SetupPopulatedStream() - { - _eventStore = EventStoreHelpers.WireupEventStore(); - SeedStore(_eventStore, Data); - } - - [IterationSetup(Target = nameof(OpenPopulatedStreamAsync))] - public void SetupPopulatedStreamAsync() - { - _eventStore = EventStoreHelpers.WireupEventStore(); - SeedStore(_eventStore, Data); - } - - [IterationCleanup] - public void IterationCleanup() - { - _eventStore?.Dispose(); - _eventStore = null; - } - - [Benchmark] - public void OpenEmptyStream() - { - using var stream = EventStore.OpenStream(StreamId, 0, int.MaxValue); - } - - [Benchmark] - public async Task OpenEmptyStreamAsync() - { - using var stream = await EventStore.OpenStreamAsync(StreamId, 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); - } - - [Benchmark] - public void OpenPopulatedStream() - { - using var stream = EventStore.OpenStream(StreamId, 0, int.MaxValue); - } - - [Benchmark] - public async Task OpenPopulatedStreamAsync() - { - using var stream = await EventStore.OpenStreamAsync(StreamId, 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); - } - - private IStoreEvents EventStore => _eventStore ?? throw new InvalidOperationException("Benchmark store has not been initialized."); - - private PreGeneratedStreamData Data => _data ?? throw new InvalidOperationException("Benchmark data has not been initialized."); - - private static void SeedStore(IStoreEvents eventStore, PreGeneratedStreamData data) - { - using var stream = eventStore.CreateStream(StreamId); - for (var i = 0; i < data.CommitIds.Length; i++) - { - stream.Add(new EventMessage { Body = data.EventBodies[i] }); - stream.CommitChanges(data.CommitIds[i]); - } - } - } -} diff --git a/src/NEventStore.Benchmark/Benchmarks/StreamWriteBenchmarks.cs b/src/NEventStore.Benchmark/Benchmarks/StreamWriteBenchmarks.cs deleted file mode 100644 index e2aeb205c..000000000 --- a/src/NEventStore.Benchmark/Benchmarks/StreamWriteBenchmarks.cs +++ /dev/null @@ -1,74 +0,0 @@ -using BenchmarkDotNet.Attributes; -using NEventStore.Benchmark.Support; - -namespace NEventStore.Benchmark.Benchmarks -{ - [Config(typeof(AllowNonOptimized))] - [SimpleJob(launchCount: 3, warmupCount: 3, iterationCount: 3, invocationCount: 1)] - [MemoryDiagnoser] - [MeanColumn, StdErrorColumn, StdDevColumn, MinColumn, MaxColumn, IterationsColumn] - public class StreamWriteBenchmarks - { - private const int StreamsPerInvocation = 320; - private IStoreEvents? _eventStore; - private PreGeneratedStreamData? _data; - private string[] _streamIds = []; - - [Params(100, 1000)] - public int CommitCount { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - _data = PreGeneratedStreamData.Create(CommitCount); - _streamIds = Enumerable.Range(0, StreamsPerInvocation) - .Select(i => $"write-benchmark-stream-{i:D4}") - .ToArray(); - } - - [IterationSetup] - public void IterationSetup() - { - _eventStore = EventStoreHelpers.WireupEventStore(); - } - - [IterationCleanup] - public void IterationCleanup() - { - _eventStore?.Dispose(); - _eventStore = null; - } - - [Benchmark(OperationsPerInvoke = StreamsPerInvocation)] - public void AppendSingleEventCommits() - { - for (var streamIndex = 0; streamIndex < StreamsPerInvocation; streamIndex++) - { - using var stream = EventStore.CreateStream(_streamIds[streamIndex]); - for (var i = 0; i < Data.CommitIds.Length; i++) - { - stream.Add(new EventMessage { Body = Data.EventBodies[i] }); - stream.CommitChanges(Data.CommitIds[i]); - } - } - } - - [Benchmark(OperationsPerInvoke = StreamsPerInvocation)] - public async Task AppendSingleEventCommitsAsync() - { - for (var streamIndex = 0; streamIndex < StreamsPerInvocation; streamIndex++) - { - using var stream = await EventStore.OpenStreamAsync(_streamIds[streamIndex], 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); - for (var i = 0; i < Data.CommitIds.Length; i++) - { - stream.Add(new EventMessage { Body = Data.EventBodies[i] }); - await stream.CommitChangesAsync(Data.CommitIds[i], CancellationToken.None).ConfigureAwait(false); - } - } - } - - private IStoreEvents EventStore => _eventStore ?? throw new InvalidOperationException("Benchmark store has not been initialized."); - - private PreGeneratedStreamData Data => _data ?? throw new InvalidOperationException("Benchmark data has not been initialized."); - } -} diff --git a/src/NEventStore.Benchmark/Benchmarks/StreamWriteLargeBenchmarks.cs b/src/NEventStore.Benchmark/Benchmarks/StreamWriteLargeBenchmarks.cs deleted file mode 100644 index 1b92b8d2c..000000000 --- a/src/NEventStore.Benchmark/Benchmarks/StreamWriteLargeBenchmarks.cs +++ /dev/null @@ -1,74 +0,0 @@ -using BenchmarkDotNet.Attributes; -using NEventStore.Benchmark.Support; - -namespace NEventStore.Benchmark.Benchmarks -{ - [Config(typeof(AllowNonOptimized))] - [SimpleJob(launchCount: 3, warmupCount: 3, iterationCount: 3, invocationCount: 1)] - [MemoryDiagnoser] - [MeanColumn, StdErrorColumn, StdDevColumn, MinColumn, MaxColumn, IterationsColumn] - public class StreamWriteLargeBenchmarks - { - private const int StreamsPerInvocation = 8; - private IStoreEvents? _eventStore; - private PreGeneratedStreamData? _data; - private string[] _streamIds = []; - - [Params(10000, 100000)] - public int CommitCount { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - _data = PreGeneratedStreamData.Create(CommitCount); - _streamIds = Enumerable.Range(0, StreamsPerInvocation) - .Select(i => $"write-large-benchmark-stream-{i:D2}") - .ToArray(); - } - - [IterationSetup] - public void IterationSetup() - { - _eventStore = EventStoreHelpers.WireupEventStore(); - } - - [IterationCleanup] - public void IterationCleanup() - { - _eventStore?.Dispose(); - _eventStore = null; - } - - [Benchmark(OperationsPerInvoke = StreamsPerInvocation)] - public void AppendSingleEventCommits() - { - for (var streamIndex = 0; streamIndex < StreamsPerInvocation; streamIndex++) - { - using var stream = EventStore.CreateStream(_streamIds[streamIndex]); - for (var i = 0; i < Data.CommitIds.Length; i++) - { - stream.Add(new EventMessage { Body = Data.EventBodies[i] }); - stream.CommitChanges(Data.CommitIds[i]); - } - } - } - - [Benchmark(OperationsPerInvoke = StreamsPerInvocation)] - public async Task AppendSingleEventCommitsAsync() - { - for (var streamIndex = 0; streamIndex < StreamsPerInvocation; streamIndex++) - { - using var stream = await EventStore.OpenStreamAsync(_streamIds[streamIndex], 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); - for (var i = 0; i < Data.CommitIds.Length; i++) - { - stream.Add(new EventMessage { Body = Data.EventBodies[i] }); - await stream.CommitChangesAsync(Data.CommitIds[i], CancellationToken.None).ConfigureAwait(false); - } - } - } - - private IStoreEvents EventStore => _eventStore ?? throw new InvalidOperationException("Benchmark store has not been initialized."); - - private PreGeneratedStreamData Data => _data ?? throw new InvalidOperationException("Benchmark data has not been initialized."); - } -} diff --git a/src/NEventStore.Benchmark/NEventStore.Benchmark.csproj b/src/NEventStore.Benchmark/NEventStore.Benchmark.csproj deleted file mode 100644 index ca53b2aa7..000000000 --- a/src/NEventStore.Benchmark/NEventStore.Benchmark.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - true - false - - exe - - Exe - - - - TRACE;DEBUG - - - - - - - - - - - - - - - - diff --git a/src/NEventStore.Benchmark/Program.cs b/src/NEventStore.Benchmark/Program.cs deleted file mode 100644 index fb847f0cf..000000000 --- a/src/NEventStore.Benchmark/Program.cs +++ /dev/null @@ -1,46 +0,0 @@ -using BenchmarkDotNet.Running; - -namespace NEventStore.Benchmark -{ - public static class Program - { - public static void Main(string[] args) - { - var switcher = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly); - - if (args.Length > 0) - { - switcher.Run(args); - return; - } - - ShowMenu(); - - while (true) - { - Console.Write("Select benchmark execution mode [1-2]: "); - var selection = Console.ReadLine(); - - switch (selection) - { - case "1": - switcher.Run([]); - return; - case "2": - switcher.RunAllJoined(config: null, args: []); - return; - default: - Console.WriteLine("Invalid selection. Enter 1 for Run or 2 for RunAllJoined."); - break; - } - } - } - - private static void ShowMenu() - { - Console.WriteLine("Benchmark execution mode:"); - Console.WriteLine(" 1. Run"); - Console.WriteLine(" 2. RunAllJoined"); - } - } -} diff --git a/src/NEventStore.Benchmark/Support/AllowNonOptimized.cs b/src/NEventStore.Benchmark/Support/AllowNonOptimized.cs deleted file mode 100644 index c124b7693..000000000 --- a/src/NEventStore.Benchmark/Support/AllowNonOptimized.cs +++ /dev/null @@ -1,18 +0,0 @@ -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Validators; -using System.Linq; - -namespace NEventStore.Benchmark.Support -{ - public class AllowNonOptimized : ManualConfig - { - public AllowNonOptimized() - { - ArtifactsPath = BenchmarkPaths.ResolveArtifactsPath(); - AddValidator(JitOptimizationsValidator.DontFailOnError); // ALLOW NON-OPTIMIZED DLLS - - AddLogger(DefaultConfig.Instance.GetLoggers().ToArray()); // manual config has no loggers by default - AddColumnProvider(DefaultConfig.Instance.GetColumnProviders().ToArray()); // manual config has no columns by default - } - } -} diff --git a/src/NEventStore.Benchmark/Support/BenchmarkPaths.cs b/src/NEventStore.Benchmark/Support/BenchmarkPaths.cs deleted file mode 100644 index b4195a0da..000000000 --- a/src/NEventStore.Benchmark/Support/BenchmarkPaths.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.IO; - -namespace NEventStore.Benchmark.Support -{ - internal static class BenchmarkPaths - { - internal static string ResolveArtifactsPath() - { - for (var current = new DirectoryInfo(AppContext.BaseDirectory); current is not null; current = current.Parent) - { - if (!File.Exists(Path.Combine(current.FullName, "src", "NEventStore.Core.sln"))) - { - continue; - } - - return Path.Combine(current.FullName, "artifacts", "benchmarks"); - } - - return Path.Combine(AppContext.BaseDirectory, "BenchmarkDotNet.Artifacts"); - } - } -} diff --git a/src/NEventStore.Benchmark/Support/EventStoreHelpers.cs b/src/NEventStore.Benchmark/Support/EventStoreHelpers.cs deleted file mode 100644 index 1bed8ce2d..000000000 --- a/src/NEventStore.Benchmark/Support/EventStoreHelpers.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NEventStore.Benchmark.Support -{ - internal static class EventStoreHelpers - { - internal static IStoreEvents WireupEventStore() - { - return Wireup.Init() - // .LogToOutputWindow(LogLevel.Verbose) - // .LogToConsoleWindow(LogLevel.Verbose) - .UsingInMemoryPersistence() - .InitializeStorageEngine() -#if NET462 - .TrackPerformanceInstance("example") -#endif - // .HookIntoPipelineUsing(new[] { new AuthorizationPipelineHook() }) - .Build(); - } - } -} diff --git a/src/NEventStore.Benchmark/Support/PreGeneratedStreamData.cs b/src/NEventStore.Benchmark/Support/PreGeneratedStreamData.cs deleted file mode 100644 index 428def951..000000000 --- a/src/NEventStore.Benchmark/Support/PreGeneratedStreamData.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Globalization; - -namespace NEventStore.Benchmark.Support -{ - internal sealed class PreGeneratedStreamData - { - private PreGeneratedStreamData(Guid[] commitIds, SomeDomainEvent[] eventBodies) - { - CommitIds = commitIds; - EventBodies = eventBodies; - } - - internal Guid[] CommitIds { get; } - - internal SomeDomainEvent[] EventBodies { get; } - - internal static PreGeneratedStreamData Create(int commitCount) - { - var commitIds = new Guid[commitCount]; - var eventBodies = new SomeDomainEvent[commitCount]; - - for (var i = 0; i < commitCount; i++) - { - commitIds[i] = Guid.NewGuid(); - eventBodies[i] = new SomeDomainEvent { Value = i.ToString(CultureInfo.InvariantCulture) }; - } - - return new PreGeneratedStreamData(commitIds, eventBodies); - } - } -} diff --git a/src/NEventStore.Benchmark/Support/SomeDomainEvent.cs b/src/NEventStore.Benchmark/Support/SomeDomainEvent.cs deleted file mode 100644 index a6cb99f3b..000000000 --- a/src/NEventStore.Benchmark/Support/SomeDomainEvent.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace NEventStore.Benchmark.Support -{ - [Serializable] - internal class SomeDomainEvent - { - public string? Value { get; set; } - } -} diff --git a/src/NEventStore.Core.sln b/src/NEventStore.Core.sln deleted file mode 100644 index 4f85c825c..000000000 --- a/src/NEventStore.Core.sln +++ /dev/null @@ -1,333 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.0.11018.127 d18.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".test", ".test", "{D3C17830-A461-4BA5-A672-461B19B33936}" - ProjectSection(SolutionItems) = preProject - NEventStore.Persistence.AcceptanceTests\PersistenceTests.Async.cs = NEventStore.Persistence.AcceptanceTests\PersistenceTests.Async.cs - NEventStore.Persistence.AcceptanceTests\PersistenceTests.cs = NEventStore.Persistence.AcceptanceTests\PersistenceTests.cs - NEventStore.Persistence.AcceptanceTests\PersistenceTests.Transactions.cs = NEventStore.Persistence.AcceptanceTests\PersistenceTests.Transactions.cs - NEventStore.Persistence.AcceptanceTests\SerializationTests.cs = NEventStore.Persistence.AcceptanceTests\SerializationTests.cs - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "serialization", "serialization", "{52F7988F-452D-46C2-A144-D85E0CF371C6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{D35087BF-941A-49EA-96A8-B10457C4D169}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Core", "NEventStore\NEventStore.Core.csproj", "{EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Persistence.AcceptanceTests.Core", "NEventStore.Persistence.AcceptanceTests\NEventStore.Persistence.AcceptanceTests.Core.csproj", "{608A1D85-0999-4A9B-956A-11B91A1CB065}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Json.Core", "NEventStore.Serialization.Json\NEventStore.Serialization.Json.Core.csproj", "{9A46374D-029C-4B87-88E3-0BE5D1D44C35}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Json.Core.Tests", "NEventStore.Serialization.Json.Tests\NEventStore.Serialization.Json.Core.Tests.csproj", "{D7029339-3794-4275-893A-E705D872BFBE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Bson.Core.Tests", "NEventStore.Serialization.Bson.Tests\NEventStore.Serialization.Bson.Core.Tests.csproj", "{2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.GZip.Core.Tests", "NEventStore.Serialization.Gzip.Tests\NEventStore.Serialization.GZip.Core.Tests.csproj", "{BCCDD838-8368-418D-82B5-5389FDDF0ADD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Core.Tests", "NEventStore.Tests\NEventStore.Core.Tests.csproj", "{8860810C-5E46-4E60-8924-C6C77BBC6EB2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{407B4AC0-6C11-4123-9BBA-094B2CADCD3A}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - ..\appveyor.yml = ..\appveyor.yml - ..\build.ps1 = ..\build.ps1 - ..\Changelog.md = ..\Changelog.md - Directory.Build.props = Directory.Build.props - ..\license.txt = ..\license.txt - ..\Readme.md = ..\Readme.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Binary.Core.Tests", "NEventStore.Serialization.Binary.Tests\NEventStore.Serialization.Binary.Core.Tests.csproj", "{9F0BFA49-8711-4143-B813-ACD0D0B003FD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Rijndael.Core.Tests", "NEventStore.Serialization.Rijndael.Tests\NEventStore.Serialization.Rijndael.Core.Tests.csproj", "{56B15268-DEBC-4E9F-8554-03737A39D71B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Benchmark", "NEventStore.Benchmark\NEventStore.Benchmark.csproj", "{2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Example", "NEventStore.Example\NEventStore.Example.csproj", "{04174210-4A5D-4F26-883E-B52B7B167EAC}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.PollingClientExample", "NEventStore.PollingClientExample\NEventStore.PollingClientExample.csproj", "{310733D1-2BF4-44D7-9947-93E29FA44310}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.Serialization.Bson.Core", "NEventStore.Serialization.Bson\NEventStore.Serialization.Bson.Core.csproj", "{C45058FC-A657-4D13-AF2A-6E0B30C444A6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NEventStore.PollingClient", "NEventStore.PollingClient\NEventStore.PollingClient.csproj", "{1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NEventStore.Serialization.MsgPack.Core", "NEventStore.Serialization.MsgPack\NEventStore.Serialization.MsgPack.Core.csproj", "{187E9876-28EC-43D7-B70E-2C09E66D59BD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NEventStore.Serialization.MsgPack.Core.Tests", "NEventStore.Serialization.MsgPack.Tests\NEventStore.Serialization.MsgPack.Core.Tests.csproj", "{7FD67842-0549-4062-ACAB-3C417BD218C1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NEventStore.Serialization.Binary.Core", "NEventStore.Serialization.Binary\NEventStore.Serialization.Binary.Core.csproj", "{26297E78-023D-4F3C-4F39-A7CC4FA9AF21}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NEventStore.Serialization.SystemTextJson.Core", "NEventStore.Serialization.SystemTextJson\NEventStore.Serialization.SystemTextJson.Core.csproj", "{DB81BF99-35E0-456E-352C-A1C53D6E204D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NEventStore.Serialization.SystemTextJson.Core.Tests", "NEventStore.Serialization.SystemTextJson.Tests\NEventStore.Serialization.SystemTextJson.Core.Tests.csproj", "{A99C7459-DF74-52C4-06D6-C9143067F30D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|x64.ActiveCfg = Debug|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|x64.Build.0 = Debug|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|x86.ActiveCfg = Debug|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Debug|x86.Build.0 = Debug|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|Any CPU.Build.0 = Release|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|x64.ActiveCfg = Release|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|x64.Build.0 = Release|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|x86.ActiveCfg = Release|Any CPU - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C}.Release|x86.Build.0 = Release|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|Any CPU.Build.0 = Debug|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|x64.ActiveCfg = Debug|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|x64.Build.0 = Debug|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|x86.ActiveCfg = Debug|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Debug|x86.Build.0 = Debug|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|Any CPU.ActiveCfg = Release|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|Any CPU.Build.0 = Release|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|x64.ActiveCfg = Release|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|x64.Build.0 = Release|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|x86.ActiveCfg = Release|Any CPU - {608A1D85-0999-4A9B-956A-11B91A1CB065}.Release|x86.Build.0 = Release|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|x64.ActiveCfg = Debug|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|x64.Build.0 = Debug|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|x86.ActiveCfg = Debug|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Debug|x86.Build.0 = Debug|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|Any CPU.Build.0 = Release|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|x64.ActiveCfg = Release|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|x64.Build.0 = Release|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|x86.ActiveCfg = Release|Any CPU - {9A46374D-029C-4B87-88E3-0BE5D1D44C35}.Release|x86.Build.0 = Release|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Debug|x64.ActiveCfg = Debug|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Debug|x64.Build.0 = Debug|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Debug|x86.ActiveCfg = Debug|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Debug|x86.Build.0 = Debug|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Release|Any CPU.Build.0 = Release|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Release|x64.ActiveCfg = Release|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Release|x64.Build.0 = Release|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Release|x86.ActiveCfg = Release|Any CPU - {D7029339-3794-4275-893A-E705D872BFBE}.Release|x86.Build.0 = Release|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|x64.ActiveCfg = Debug|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|x64.Build.0 = Debug|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|x86.ActiveCfg = Debug|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Debug|x86.Build.0 = Debug|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|Any CPU.Build.0 = Release|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|x64.ActiveCfg = Release|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|x64.Build.0 = Release|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|x86.ActiveCfg = Release|Any CPU - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A}.Release|x86.Build.0 = Release|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|x64.ActiveCfg = Debug|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|x64.Build.0 = Debug|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|x86.ActiveCfg = Debug|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Debug|x86.Build.0 = Debug|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|Any CPU.Build.0 = Release|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|x64.ActiveCfg = Release|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|x64.Build.0 = Release|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|x86.ActiveCfg = Release|Any CPU - {BCCDD838-8368-418D-82B5-5389FDDF0ADD}.Release|x86.Build.0 = Release|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|x64.ActiveCfg = Debug|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|x64.Build.0 = Debug|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|x86.ActiveCfg = Debug|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Debug|x86.Build.0 = Debug|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|Any CPU.Build.0 = Release|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|x64.ActiveCfg = Release|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|x64.Build.0 = Release|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|x86.ActiveCfg = Release|Any CPU - {8860810C-5E46-4E60-8924-C6C77BBC6EB2}.Release|x86.Build.0 = Release|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|x64.ActiveCfg = Debug|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|x64.Build.0 = Debug|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|x86.ActiveCfg = Debug|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Debug|x86.Build.0 = Debug|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|Any CPU.Build.0 = Release|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|x64.ActiveCfg = Release|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|x64.Build.0 = Release|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|x86.ActiveCfg = Release|Any CPU - {9F0BFA49-8711-4143-B813-ACD0D0B003FD}.Release|x86.Build.0 = Release|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|x64.ActiveCfg = Debug|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|x64.Build.0 = Debug|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|x86.ActiveCfg = Debug|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Debug|x86.Build.0 = Debug|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|Any CPU.Build.0 = Release|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|x64.ActiveCfg = Release|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|x64.Build.0 = Release|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|x86.ActiveCfg = Release|Any CPU - {56B15268-DEBC-4E9F-8554-03737A39D71B}.Release|x86.Build.0 = Release|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|x64.ActiveCfg = Debug|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|x64.Build.0 = Debug|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|x86.ActiveCfg = Debug|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Debug|x86.Build.0 = Debug|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|Any CPU.Build.0 = Release|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|x64.ActiveCfg = Release|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|x64.Build.0 = Release|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|x86.ActiveCfg = Release|Any CPU - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7}.Release|x86.Build.0 = Release|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|x64.ActiveCfg = Debug|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|x64.Build.0 = Debug|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|x86.ActiveCfg = Debug|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Debug|x86.Build.0 = Debug|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|Any CPU.Build.0 = Release|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|x64.ActiveCfg = Release|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|x64.Build.0 = Release|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|x86.ActiveCfg = Release|Any CPU - {04174210-4A5D-4F26-883E-B52B7B167EAC}.Release|x86.Build.0 = Release|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|Any CPU.Build.0 = Debug|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|x64.ActiveCfg = Debug|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|x64.Build.0 = Debug|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|x86.ActiveCfg = Debug|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Debug|x86.Build.0 = Debug|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|Any CPU.ActiveCfg = Release|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|Any CPU.Build.0 = Release|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|x64.ActiveCfg = Release|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|x64.Build.0 = Release|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|x86.ActiveCfg = Release|Any CPU - {310733D1-2BF4-44D7-9947-93E29FA44310}.Release|x86.Build.0 = Release|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|x64.ActiveCfg = Debug|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|x64.Build.0 = Debug|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|x86.ActiveCfg = Debug|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Debug|x86.Build.0 = Debug|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|Any CPU.Build.0 = Release|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|x64.ActiveCfg = Release|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|x64.Build.0 = Release|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|x86.ActiveCfg = Release|Any CPU - {C45058FC-A657-4D13-AF2A-6E0B30C444A6}.Release|x86.Build.0 = Release|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|x64.ActiveCfg = Debug|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|x64.Build.0 = Debug|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|x86.ActiveCfg = Debug|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Debug|x86.Build.0 = Debug|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|Any CPU.Build.0 = Release|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|x64.ActiveCfg = Release|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|x64.Build.0 = Release|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|x86.ActiveCfg = Release|Any CPU - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6}.Release|x86.Build.0 = Release|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|x64.ActiveCfg = Debug|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|x64.Build.0 = Debug|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|x86.ActiveCfg = Debug|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Debug|x86.Build.0 = Debug|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|Any CPU.Build.0 = Release|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|x64.ActiveCfg = Release|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|x64.Build.0 = Release|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|x86.ActiveCfg = Release|Any CPU - {187E9876-28EC-43D7-B70E-2C09E66D59BD}.Release|x86.Build.0 = Release|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|x64.ActiveCfg = Debug|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|x64.Build.0 = Debug|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|x86.ActiveCfg = Debug|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Debug|x86.Build.0 = Debug|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|Any CPU.Build.0 = Release|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|x64.ActiveCfg = Release|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|x64.Build.0 = Release|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|x86.ActiveCfg = Release|Any CPU - {7FD67842-0549-4062-ACAB-3C417BD218C1}.Release|x86.Build.0 = Release|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|Any CPU.Build.0 = Debug|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|x64.ActiveCfg = Debug|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|x64.Build.0 = Debug|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|x86.ActiveCfg = Debug|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Debug|x86.Build.0 = Debug|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|Any CPU.ActiveCfg = Release|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|Any CPU.Build.0 = Release|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|x64.ActiveCfg = Release|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|x64.Build.0 = Release|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|x86.ActiveCfg = Release|Any CPU - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21}.Release|x86.Build.0 = Release|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Debug|x64.ActiveCfg = Debug|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Debug|x64.Build.0 = Debug|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Debug|x86.ActiveCfg = Debug|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Debug|x86.Build.0 = Debug|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Release|Any CPU.Build.0 = Release|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Release|x64.ActiveCfg = Release|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Release|x64.Build.0 = Release|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Release|x86.ActiveCfg = Release|Any CPU - {DB81BF99-35E0-456E-352C-A1C53D6E204D}.Release|x86.Build.0 = Release|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Debug|x64.ActiveCfg = Debug|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Debug|x64.Build.0 = Debug|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Debug|x86.ActiveCfg = Debug|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Debug|x86.Build.0 = Debug|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Release|Any CPU.Build.0 = Release|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Release|x64.ActiveCfg = Release|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Release|x64.Build.0 = Release|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Release|x86.ActiveCfg = Release|Any CPU - {A99C7459-DF74-52C4-06D6-C9143067F30D}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {EB3ABF3B-295C-4BDD-8EA2-B60F30BC5E6C} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {608A1D85-0999-4A9B-956A-11B91A1CB065} = {D3C17830-A461-4BA5-A672-461B19B33936} - {9A46374D-029C-4B87-88E3-0BE5D1D44C35} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {D7029339-3794-4275-893A-E705D872BFBE} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {2C6CE3D7-D2DF-49F7-B3AF-5767E1C3705A} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {BCCDD838-8368-418D-82B5-5389FDDF0ADD} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {8860810C-5E46-4E60-8924-C6C77BBC6EB2} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {9F0BFA49-8711-4143-B813-ACD0D0B003FD} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {56B15268-DEBC-4E9F-8554-03737A39D71B} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {2D3523BB-CF07-47D0-82F4-6B5621A8C1D7} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {04174210-4A5D-4F26-883E-B52B7B167EAC} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {310733D1-2BF4-44D7-9947-93E29FA44310} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {C45058FC-A657-4D13-AF2A-6E0B30C444A6} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {1F03D5F0-238E-48B0-BAE4-88B1E60B69C6} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {187E9876-28EC-43D7-B70E-2C09E66D59BD} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {7FD67842-0549-4062-ACAB-3C417BD218C1} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - {26297E78-023D-4F3C-4F39-A7CC4FA9AF21} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {DB81BF99-35E0-456E-352C-A1C53D6E204D} = {D35087BF-941A-49EA-96A8-B10457C4D169} - {A99C7459-DF74-52C4-06D6-C9143067F30D} = {52F7988F-452D-46C2-A144-D85E0CF371C6} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9397EEB2-E3A3-4748-A106-3DE17F40E698} - EndGlobalSection -EndGlobal diff --git a/src/NEventStore.Example/AggregateMemento.cs b/src/NEventStore.Example/AggregateMemento.cs deleted file mode 100644 index 208491aa3..000000000 --- a/src/NEventStore.Example/AggregateMemento.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NEventStore.Example -{ - internal class AggregateMemento - { - public string? Value { get; set; } - - public override string? ToString() - { - return Value; - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Example/AuthorizationPipelineHook.cs b/src/NEventStore.Example/AuthorizationPipelineHook.cs deleted file mode 100644 index 6563205ba..000000000 --- a/src/NEventStore.Example/AuthorizationPipelineHook.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NEventStore.Example -{ - public class AuthorizationPipelineHook : PipelineHookBase - { - public override ICommit? SelectCommit(ICommit committed) - { - // return null if the user isn't authorized to see this commit - return committed; - } - - public override void PostCommit(ICommit committed) - { - // anything to do after the commit has been persisted. - } - - public override bool PreCommit(CommitAttempt attempt) - { - // Can easily do logging or other such activities here - return true; // true == allow commit to continue, false = stop. - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Example/MainProgram.cs b/src/NEventStore.Example/MainProgram.cs deleted file mode 100644 index 1197b095a..000000000 --- a/src/NEventStore.Example/MainProgram.cs +++ /dev/null @@ -1,157 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace NEventStore.Example -{ - internal static class MainProgram - { - private static readonly Guid StreamId = Guid.NewGuid(); // aggregate identifier - - private static IStoreEvents? store; - - private static void Main() - { - // Console.WindowWidth = Console.LargestWindowWidth - 20; - - Console.WriteLine("------------------"); - Console.WriteLine("Using Sync Methods"); - Console.WriteLine("------------------"); - Console.WriteLine(); - - using (store = WireupEventStore()) - { - OpenOrCreateStream(); - AppendToStream(); - TakeSnapshot(); - LoadFromSnapshotForwardAndAppend(); - } - - Console.WriteLine(); - Console.WriteLine("-------------------"); - Console.WriteLine("Using Async Methods"); - Console.WriteLine("-------------------"); - Console.WriteLine(); - - Task.Run(async () => - { - using (store = WireupEventStore()) - { - await OpenOrCreateStreamAsync(); - await AppendToStreamAsync(); - await TakeSnapshotAsync(); - await LoadFromSnapshotForwardAndAppendAsync(); - } - }).GetAwaiter().GetResult(); - - Console.WriteLine("Press any key to continue..."); - Console.ReadKey(); - } - - private static IStoreEvents WireupEventStore() - { - var loggerFactory = LoggerFactory.Create(logging => - { - logging - .AddConsole() - .AddDebug() - .SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); - }); - - return Wireup.Init() - .WithLoggerFactory(loggerFactory) - .UseOptimisticPipelineHook() - .UsingInMemoryPersistence() - .InitializeStorageEngine() -#if NET462 - .TrackPerformanceInstance("example") -#endif - .HookIntoPipelineUsing(new AuthorizationPipelineHook()) - .Build(); - } - - #region Sync Methods - - private static void OpenOrCreateStream() - { - // we can call CreateStream(StreamId) if we know there isn't going to be any data. - // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, - // if no commits exist then it creates a new stream for us. - using var stream = store!.OpenStream(StreamId, 0, int.MaxValue); - var @event = new SomeDomainEvent { Value = "Initial event." }; - - stream.Add(new EventMessage { Body = @event }); - stream.CommitChanges(Guid.NewGuid()); - } - - private static void AppendToStream() - { - using var stream = store!.OpenStream(StreamId); - var @event = new SomeDomainEvent { Value = "Second event." }; - - stream.Add(new EventMessage { Body = @event }); - stream.CommitChanges(Guid.NewGuid()); - } - - private static void TakeSnapshot() - { - var memento = new AggregateMemento { Value = "snapshot" }; - store!.Advanced.AddSnapshot(new Snapshot(StreamId.ToString(), 2, memento)); - } - - private static void LoadFromSnapshotForwardAndAppend() - { - var latestSnapshot = store!.Advanced.GetSnapshot(StreamId, int.MaxValue) - ?? throw new InvalidOperationException("No snapshot found."); - - using var stream = store.OpenStream(latestSnapshot, int.MaxValue); - var @event = new SomeDomainEvent { Value = "Third event (first one after a snapshot)." }; - - stream.Add(new EventMessage { Body = @event }); - stream.CommitChanges(Guid.NewGuid()); - } - - #endregion - - #region Async Methods - - private static async Task OpenOrCreateStreamAsync() - { - // we can call CreateStream(StreamId) if we know there isn't going to be any data. - // or we can call OpenStream(StreamId, 0, int.MaxValue) to read all commits, - // if no commits exist then it creates a new stream for us. - using var stream = await store!.OpenStreamAsync(StreamId, 0, int.MaxValue, cancellationToken: CancellationToken.None); - var @event = new SomeDomainEvent { Value = "Initial event." }; - - stream.Add(new EventMessage { Body = @event }); - await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None); - } - - private static async Task AppendToStreamAsync() - { - using var stream = await store!.OpenStreamAsync(StreamId, cancellationToken: CancellationToken.None); - var @event = new SomeDomainEvent { Value = "Second event." }; - - stream.Add(new EventMessage { Body = @event }); - await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None); - } - - private static Task TakeSnapshotAsync() - { - var memento = new AggregateMemento { Value = "snapshot" }; - return store!.Advanced.AddSnapshotAsync(new Snapshot(StreamId.ToString(), 2, memento), CancellationToken.None); - } - - private static async Task LoadFromSnapshotForwardAndAppendAsync() - { - var latestSnapshot = await store!.Advanced.GetSnapshotAsync(StreamId, int.MaxValue, CancellationToken.None) - ?? throw new InvalidOperationException("No snapshot found."); - - using var stream = await store.OpenStreamAsync(latestSnapshot, int.MaxValue, CancellationToken.None); - var @event = new SomeDomainEvent { Value = "Third event (first one after a snapshot)." }; - - stream.Add(new EventMessage { Body = @event }); - await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None); - } - - #endregion - } -} \ No newline at end of file diff --git a/src/NEventStore.Example/NEventStore.Example.csproj b/src/NEventStore.Example/NEventStore.Example.csproj deleted file mode 100644 index 692020d35..000000000 --- a/src/NEventStore.Example/NEventStore.Example.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - false - - exe - - Exe - - - - TRACE;DEBUG - - - - - - - - - - - - - - - - - - diff --git a/src/NEventStore.Example/Properties/ProjectAssemblyInfo.cs b/src/NEventStore.Example/Properties/ProjectAssemblyInfo.cs deleted file mode 100644 index 33cbc2e74..000000000 --- a/src/NEventStore.Example/Properties/ProjectAssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("NEventStore.Example")] -[assembly: AssemblyDescription("")] -[assembly: Guid("6fd621e0-9047-4449-b136-249383936d5c")] \ No newline at end of file diff --git a/src/NEventStore.Example/SomeDomainEvent.cs b/src/NEventStore.Example/SomeDomainEvent.cs deleted file mode 100644 index b83e751f9..000000000 --- a/src/NEventStore.Example/SomeDomainEvent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NEventStore.Example -{ - internal class SomeDomainEvent - { - public string? Value { get; set; } - } -} \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/BDD/MSTest/SpecificationBase.cs b/src/NEventStore.Persistence.AcceptanceTests/BDD/MSTest/SpecificationBase.cs deleted file mode 100644 index 43f014b67..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/BDD/MSTest/SpecificationBase.cs +++ /dev/null @@ -1,108 +0,0 @@ -#if MSTEST - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NEventStore.Persistence.AcceptanceTests.BDD -{ - /// - /// - /// base class for BDD testing in a Given-When-Then style - /// using MSTest - /// - /// in MSTest each test will be executed by a new instance of the test class. - /// - /// this will be used to implement a class that will test a single - /// action or behavior and multiple result conditions - /// - /// - /// - a class will represent a single scenario - /// - the class name will describe the scenario name - /// - /// - [TestClass] - public abstract class SpecificationBase - { - Exception? testFixtureSetupException = null; - - /// - /// - /// there's a problem with error / exception reporting in here, test fixture setup is not well suited for - /// exception handling - /// workaround: - /// http://stackoverflow.com/questions/1411676/how-to-diagnose-testfixturesetup-failed - /// - /// - /// a good idea on how to catch and test for exceptions: - /// http://www.planetgeek.ch/2015/06/22/machine-specifications-the-alternative-nunit/ - /// - /// - /// maybe catch the generated exception with something like: Catch.Exception() shown here and save it to a local variable - /// in the when() function, then test for the exception in the 'then' tests - /// - /// - [TestInitialize] // I cannot have something like a OnTimeTestInitialize in MsTest, ClassInitialize requires static methods - public async Task SetUp() - { - try - { - Context(); - await ContextAsync(); - Because(); - await BecauseAsync(); - } - catch (Exception ex) - { - testFixtureSetupException = ex; - } - } - - [TestInitialize] - // NUnit doesn't support very useful logging of failures from a OneTimeSetUp method. We'll do the logging here. - public void CheckForTestFixtureFailure() - { - if (testFixtureSetupException != null) - { - string msg = string.Format("There was a failure during Context() or Because() phases.\n\rException: {0}\n\rStackTrace: {1}", - testFixtureSetupException.Message, testFixtureSetupException.StackTrace); - Assert.Fail(msg); - } - } - - protected virtual void Context() { } - protected virtual Task ContextAsync() { return Task.CompletedTask; } - protected virtual void Because() { } - protected virtual Task BecauseAsync() { return Task.CompletedTask; } - - [ClassCleanup] - protected virtual void Cleanup() { } - } - - /// - /// Attribute used to identify the tests - /// - /// for custom actions: - /// http://nunit.org/index.php?p=actionAttributes&r=2.6.3 - /// - /// - [AttributeUsage(AttributeTargets.Method)] - public class ThenAttribute : LogTestMethod; - - [AttributeUsage(AttributeTargets.Method)] - public class FactAttribute : LogTestMethod; - - public class LogTestMethod : TestMethodAttribute - { - public override TestResult[] Execute(ITestMethod testMethod) - { - Console.WriteLine("Scenario: {0}", testMethod.TestClassName); - - var result = base.Execute(testMethod); - - Console.WriteLine(" - {0} - {1}", testMethod.TestMethodName, result[0].Outcome == UnitTestOutcome.Passed); - - return result; - } - } -} - -#endif \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/BDD/NUnit/SpecificationBase.cs b/src/NEventStore.Persistence.AcceptanceTests/BDD/NUnit/SpecificationBase.cs deleted file mode 100644 index 93c467afa..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/BDD/NUnit/SpecificationBase.cs +++ /dev/null @@ -1,147 +0,0 @@ -#if NUNIT - -using NUnit.Framework; -using NUnit.Framework.Interfaces; -using Assert = NUnit.Framework.Assert; -using TestContext = NUnit.Framework.TestContext; - -namespace NEventStore.Persistence.AcceptanceTests.BDD -{ - /// - /// - /// base class for BDD testing in a Given-When-Then style - /// using NUnit - /// - /// - /// this will be used to implement a class that will test a single - /// action or behavior and multiple result conditions: - /// - a class will represent a single scenario - /// - the class name will describe the scenario name - /// - /// - [TestFixture] - [LogSuiteAttribute] - [LogTestAttribute] - public abstract class SpecificationBase - { - private Exception? testFixtureSetupException = null; - - /// - /// - /// there's a problem with error / exception reporting in here, test fixture setup is not well suited for - /// exception handling - /// workaround: - /// http://stackoverflow.com/questions/1411676/how-to-diagnose-testfixturesetup-failed - /// - /// - /// a good idea on how to catch and test for exceptions: - /// http://www.planetgeek.ch/2015/06/22/machine-specifications-the-alternative-nunit/ - /// - /// - /// maybe catch the generated exception with something like: Catch.Exception() shown here and save it to a local variable - /// in the when() function, then test for the exception in the 'then' tests - /// - /// - [OneTimeSetUp] - public async Task SetUp() - { - try - { - Context(); - await ContextAsync(); - Because(); - await BecauseAsync(); - } - catch (Exception ex) - { - testFixtureSetupException = ex; - } - } - - [SetUp] - // NUnit doesn't support very useful logging of failures from a OneTimeSetUp method. We'll do the logging here. - public void CheckForTestFixtureFailure() - { - if (testFixtureSetupException != null) - { - string msg = string.Format("There was a failure during Context() or Because() phases.\n\rException: {0}\n\rStackTrace: {1}", - testFixtureSetupException.Message, testFixtureSetupException.StackTrace); - Assert.Fail(msg); - } - } - - protected virtual void Context() { } - protected virtual Task ContextAsync() { return Task.CompletedTask; } - protected virtual void Because() { } - protected virtual Task BecauseAsync() { return Task.CompletedTask; } - - [OneTimeTearDown] - protected virtual void Cleanup() { } - } - - /// - /// - /// Attribute used to identify the tests - /// - /// - /// for custom actions: - /// http://nunit.org/index.php?p=actionAttributes&r=2.6.3 - /// - /// - [AttributeUsage(AttributeTargets.Method)] - public class ThenAttribute : TestAttribute; - - [AttributeUsage(AttributeTargets.Method)] - public class FactAttribute : TestAttribute; - - [AttributeUsageAttribute(AttributeTargets.Class)] - public class LogSuiteAttribute : Attribute, ITestAction - { - public void AfterTest(ITest test) - { - // Method intentionally left empty. - } - - public void BeforeTest(ITest test) - { - Console.WriteLine("Scenario: {0}", test?.Fixture?.GetType().Name); - } - - public ActionTargets Targets - { - get { return ActionTargets.Suite; } - } - } - - /// - /// - /// Attribute used to identify the tests - /// and describe them - /// - /// - /// for custom actions: - /// http://nunit.org/index.php?p=actionAttributes&r=2.6.3 - /// http://nunit.org/index.php?p=testContext&r=2.6.3 - /// - /// - [AttributeUsageAttribute(AttributeTargets.Class, AllowMultiple = false)] - public class LogTestAttribute : Attribute, ITestAction - { - public void AfterTest(ITest test) - { - Console.WriteLine(" - {0} - {1}", test?.Method?.Name, TestContext.CurrentContext.Result.Outcome.Status); - } - - public void BeforeTest(ITest test) - { - Console.WriteLine(test?.Fixture?.GetType().Name); - } - - public ActionTargets Targets - { - get { return ActionTargets.Test; } - } - } -} - -#endif \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBase.cs b/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBase.cs deleted file mode 100644 index 58832237c..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBase.cs +++ /dev/null @@ -1,34 +0,0 @@ -#if XUNIT - -namespace NEventStore.Persistence.AcceptanceTests.BDD -{ - - using Xunit; - - - [RunWith(typeof (SpecificationBaseRunner))] - public abstract class SpecificationBase - { - protected virtual void Because() - {} - - protected virtual void Cleanup() - {} - - protected virtual void Context() - {} - - public void OnFinish() - { - Cleanup(); - } - - public void OnStart() - { - Context(); - Because(); - } - } -} - -#endif \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBaseRunner.cs b/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBaseRunner.cs deleted file mode 100644 index a46340a13..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/BDD/SpecificationBaseRunner.cs +++ /dev/null @@ -1,168 +0,0 @@ -#if XUNIT - -namespace NEventStore.Persistence.AcceptanceTests.BDD -{ - using System; - using System.Collections.Generic; - using System.Reflection; - using Xunit; - using Xunit.Sdk; - - internal class SpecificationBaseRunner : ITestClassCommand - { - private readonly List _fixtures = new List(); - private SpecificationBase _objectUnderTest; - - public SpecificationBase ObjectUnderTest - { - get - { - if (_objectUnderTest == null) - { - GuardTypeUnderTest(); - _objectUnderTest = (SpecificationBase) Activator.CreateInstance(TypeUnderTest.Type); - } - - return _objectUnderTest; - } - } - - object ITestClassCommand.ObjectUnderTest - { - get { return ObjectUnderTest; } - } - - public ITypeInfo TypeUnderTest { get; set; } - - public int ChooseNextTest(ICollection testsLeftToRun) - { - return 0; - } - - public Exception ClassStart() - { - try - { - SetupFixtures(); - ObjectUnderTest.OnStart(); - return null; - } - catch (Exception ex) - { - return ex; - } - } - - public Exception ClassFinish() - { - try - { - ObjectUnderTest.OnFinish(); - - foreach (var fixtureData in _fixtures) - { - var disposable = fixtureData as IDisposable; - if (disposable != null) - { - disposable.Dispose(); - } - } - - return null; - } - catch (Exception ex) - { - return ex; - } - } - - public IEnumerable EnumerateTestCommands(IMethodInfo testMethod) - { - string displayName = (TypeUnderTest.Type.Name + ", it " + testMethod.Name).Replace('_', ' '); - return new[] {new SpecTestCommand(testMethod, displayName)}; - } - - public IEnumerable EnumerateTestMethods() - { - GuardTypeUnderTest(); - - return TypeUtility.GetTestMethods(TypeUnderTest); - } - - public bool IsTestMethod(IMethodInfo testMethod) - { - return MethodUtility.IsTest(testMethod); - } - - private void SetupFixtures() - { - try - { - foreach (var @interface in TypeUnderTest.Type.GetInterfaces()) - { - if (@interface.IsGenericType) - { - Type genericDefinition = @interface.GetGenericTypeDefinition(); - - if (genericDefinition == typeof (IUseFixture<>)) - { - Type dataType = @interface.GetGenericArguments()[0]; - if (dataType == TypeUnderTest.Type) - { - throw new InvalidOperationException("Cannot use a test class as its own fixture data"); - } - - object fixtureData = null; - - fixtureData = Activator.CreateInstance(dataType); - - MethodInfo method = @interface.GetMethod("SetFixture", new[] {dataType}); - _fixtures.Add(fixtureData); - method.Invoke(ObjectUnderTest, new[] {fixtureData}); - } - } - } - } - catch (TargetInvocationException ex) - { - ExceptionUtility.RethrowWithNoStackTraceLoss(ex.InnerException); - } - } - - private void GuardTypeUnderTest() - { - if (TypeUnderTest == null) - { - throw new InvalidOperationException("Forgot to set TypeUnderTest before calling ObjectUnderTest"); - } - - if (!typeof (SpecificationBase).IsAssignableFrom(TypeUnderTest.Type)) - { - throw new InvalidOperationException("SpecificationBaseRunner can only be used with types that derive from SpecificationBase"); - } - } - - private class SpecTestCommand : TestCommand - { - public SpecTestCommand(IMethodInfo testMethod, string displayName) : base(testMethod, displayName, 0) - {} - - public override MethodResult Execute(object testClass) - { - try - { - testMethod.Invoke(testClass, null); - } - catch (ParameterCountMismatchException) - { - throw new InvalidOperationException("Observation " + TypeName + "." + MethodName + " cannot have parameters"); - } - - return new PassedResult(testMethod, DisplayName); - } - } - } - -} - -#endif \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/Catch.cs b/src/NEventStore.Persistence.AcceptanceTests/Catch.cs deleted file mode 100644 index a99a08b1d..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/Catch.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace NEventStore.Persistence.AcceptanceTests -{ - public static class Catch - { - public static Exception? Exception(Action action) - { - try - { - action(); - } - catch (Exception ex) - { - return ex; - } - - return null; - } - - public static async Task ExceptionAsync(Func action) - { - try - { - await action(); - } - catch (Exception ex) - { - return ex; - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs b/src/NEventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs deleted file mode 100644 index bc734f738..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/ConfigurationExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace NEventStore.Persistence.AcceptanceTests -{ - using System; - using System.Configuration; - using System.Linq; - - public static class ConfigurationExtensions - { - public static string GetSetting(this string settingName) - { - return GetCommandLineArgument("/" + settingName + ":") ?? - Environment.GetEnvironmentVariable(settingName) ?? ConfigurationManager.AppSettings[settingName]; - } - - private static string GetCommandLineArgument(string settingName) - { - return - Environment.GetCommandLineArgs() - .Where(arg => arg.StartsWith(settingName)) - .Select(arg => arg.Replace(settingName, string.Empty)) - .FirstOrDefault(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/ExtensionMethods.cs b/src/NEventStore.Persistence.AcceptanceTests/ExtensionMethods.cs deleted file mode 100644 index 435b434b1..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/ExtensionMethods.cs +++ /dev/null @@ -1,186 +0,0 @@ -namespace NEventStore.Persistence.AcceptanceTests -{ - public static class ExtensionMethods - { - public static HashSet ToHashSet(this IEnumerable collection) - { - return new HashSet(collection); - } - - public static LinkedList ToLinkedList(this IEnumerable collection) - { - return new LinkedList(collection); - } - - public static ICommit? CommitSingle(this IPersistStreams persistence, string? streamId = null) - { - CommitAttempt commitAttempt = (streamId ?? Guid.NewGuid().ToString()).BuildAttempt(); - return persistence.Commit(commitAttempt); - } - - public static Task CommitSingleAsync(this IPersistStreams persistence, string? streamId = null) - { - CommitAttempt commitAttempt = (streamId ?? Guid.NewGuid().ToString()).BuildAttempt(); - return persistence.CommitAsync(commitAttempt, CancellationToken.None); - } - - public static ICommit? CommitNext(this IPersistStreams persistence, ICommit previous) - { - var nextAttempt = previous.BuildNextAttempt(); - return persistence.Commit(nextAttempt); - } - - public static Task CommitNextAsync(this IPersistStreams persistence, ICommit previous) - { - var nextAttempt = previous.BuildNextAttempt(); - return persistence.CommitAsync(nextAttempt, CancellationToken.None); - } - - public static ICommit? CommitNext(this IPersistStreams persistence, CommitAttempt previous) - { - var nextAttempt = previous.BuildNextAttempt(); - return persistence.Commit(nextAttempt); - } - - public static Task CommitNextAsync(this IPersistStreams persistence, CommitAttempt previous) - { - var nextAttempt = previous.BuildNextAttempt(); - return persistence.CommitAsync(nextAttempt, CancellationToken.None); - } - - public static IEnumerable CommitMany(this IPersistStreams persistence, int numberOfCommits, string? streamId = null, string? bucketId = null) - { - var commits = new List(); - CommitAttempt? attempt = null; - - for (int i = 0; i < numberOfCommits; i++) - { - attempt = attempt == null ? (streamId ?? Guid.NewGuid().ToString()).BuildAttempt(null, bucketId) : attempt.BuildNextAttempt(); - persistence.Commit(attempt); - commits.Add(attempt); - } - - return commits; - } - - public static async Task> CommitManyAsync(this IPersistStreams persistence, int numberOfCommits, string? streamId = null, string? bucketId = null) - { - var commits = new List(); - CommitAttempt? attempt = null; - for (int i = 0; i < numberOfCommits; i++) - { - attempt = attempt == null ? (streamId ?? Guid.NewGuid().ToString()).BuildAttempt(null, bucketId) : attempt.BuildNextAttempt(); - await persistence.CommitAsync(attempt, CancellationToken.None).ConfigureAwait(false); - commits.Add(attempt); - } - return commits; - } - - public static CommitAttempt BuildAttempt(this string streamId, DateTime? now = null, string? bucketId = null) - { - now ??= SystemTime.UtcNow; - bucketId ??= Bucket.Default; - - var messages = new EventMessage[] - { - new() {Body = new SomeDomainEvent {SomeProperty = "Test"}}, - new() {Body = new SomeDomainEvent {SomeProperty = "Test2"}}, - }; - - return new CommitAttempt( - bucketId: bucketId, - streamId: streamId, - streamRevision: 2, - commitId: Guid.NewGuid(), - commitSequence: 1, - commitStamp: now.Value, - headers: new Dictionary { { "A header", "A string value" }, { "Another header", 2 } }, - events: messages - ); - } - - public static CommitAttempt BuildNextAttempt(this ICommit commit) - { - var messages = new EventMessage[] - { - new() {Body = new SomeDomainEvent {SomeProperty = "Another test"}}, - new() {Body = new SomeDomainEvent {SomeProperty = "Another test2"}}, - }; - - return new CommitAttempt(commit.BucketId, - commit.StreamId, - commit.StreamRevision + messages.Length, - Guid.NewGuid(), - commit.CommitSequence + 1, - commit.CommitStamp.AddSeconds(1), - new Dictionary(), - messages); - } - - public static CommitAttempt BuildNextAttempt(this CommitAttempt commit) - { - var messages = new EventMessage[] - { - new() {Body = new SomeDomainEvent {SomeProperty = "Another test"}}, - new() {Body = new SomeDomainEvent {SomeProperty = "Another test2"}}, - }; - - return new CommitAttempt(commit.BucketId, - commit.StreamId, - commit.StreamRevision + 2, - Guid.NewGuid(), - commit.CommitSequence + 1, - commit.CommitStamp.AddSeconds(1), - new Dictionary(), - messages); - } - - public static SimpleMessage Populate(this SimpleMessage message) - { - message ??= new SimpleMessage(); - - return new SimpleMessage - { - Id = Guid.NewGuid(), - Count = 1234, - Created = new DateTime(2000, 2, 3, 4, 5, 6, 7).ToUniversalTime(), - Value = message.Value + "Hello, World!", - Contents = { "a", null, string.Empty, "d" } - }; - } - - public static CommitAttempt BuildCommit(this string streamId) - { - const int streamRevision = 2; - const int commitSequence = 2; - Guid commitId = Guid.NewGuid(); - var headers = new Dictionary { { "Key", "Value" }, { "Key2", (long)1234 }, { "Key3", null! } }; - var events = new[] - { - new EventMessage - { - Headers = {{"MsgKey1", TimeSpan.MinValue}, {"MsgKey2", Guid.NewGuid()}, {"MsgKey3", 1.1M}, {"MsgKey4", (ushort) 1}}, - Body = "some value" - }, - new EventMessage - { - Headers = {{"MsgKey1", new Uri("http://www.google.com/")}, {"MsgKey4", "some header"}}, - Body = new[] {"message body"} - } - }; - - return new CommitAttempt(streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, headers, events); - } - - [Serializable] - public class SomeDomainEvent - { - public string? SomeProperty { get; set; } - - public override string? ToString() - { - return SomeProperty; - } - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/NEventStore.Persistence.AcceptanceTests.Core.csproj b/src/NEventStore.Persistence.AcceptanceTests/NEventStore.Persistence.AcceptanceTests.Core.csproj deleted file mode 100644 index 6a0cbec39..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/NEventStore.Persistence.AcceptanceTests.Core.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - net472;$(ModernTargetFrameworks) - false - NEventStore.Persistence.AcceptanceTests - false - NEventStore.Persistence.AcceptanceTests - - - TRACE;DEBUG;NUNIT - - - NUNIT - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Async.cs b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Async.cs deleted file mode 100644 index d0e3c8a21..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Async.cs +++ /dev/null @@ -1,1692 +0,0 @@ -#pragma warning disable 169 // ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; - -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; -#endif -#if NUNIT -using NUnit.Framework; -using Assert = NUnit.Framework.Assert; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.Persistence.AcceptanceTests.Async -{ -#if MSTEST - [TestClass] -#endif - public class when_a_commit_header_has_a_name_that_contains_a_period : PersistenceEngineConcernAsync - { - private ICommit? _persisted; - private string? _streamId; - - protected override Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - var attempt = new CommitAttempt(_streamId, - 2, - Guid.NewGuid(), - 1, - DateTime.UtcNow, - new Dictionary { { "key.1", "value" } }, - [new EventMessage { Body = new ExtensionMethods.SomeDomainEvent { SomeProperty = "Test" } }]); - return Persistence.CommitAsync(attempt, CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_streamId!, 0, int.MaxValue, observer, CancellationToken.None); - _persisted = observer.Commits[0]; - } - - [Fact] - public void should_correctly_deserialize_headers() - { - _persisted.Should().NotBeNull(); - _persisted!.Headers.Keys.Should().Contain("key.1"); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_a_commit_is_successfully_persisted : PersistenceEngineConcernAsync - { - private CommitAttempt? _attempt; - private DateTime _now; - private ICommit? _persisted; - private string? _streamId; - - protected override Task ContextAsync() - { - _now = SystemTime.UtcNow.AddYears(1); - _streamId = Guid.NewGuid().ToString(); - _attempt = _streamId.BuildAttempt(_now); - - return Persistence.CommitAsync(_attempt, CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_streamId!, 0, int.MaxValue, observer, CancellationToken.None); - _persisted = observer.Commits[0]; - } - - [Fact] - public void should_correctly_persist_the_stream_identifier() - { - _persisted!.StreamId.Should().Be(_attempt!.StreamId); - } - - [Fact] - public void should_correctly_persist_the_stream_stream_revision() - { - _persisted!.StreamRevision.Should().Be(_attempt!.StreamRevision); - } - - [Fact] - public void should_correctly_persist_the_commit_identifier() - { - _persisted!.CommitId.Should().Be(_attempt!.CommitId); - } - - [Fact] - public void should_correctly_persist_the_commit_sequence() - { - _persisted!.CommitSequence.Should().Be(_attempt!.CommitSequence); - } - - // persistence engines have varying levels of precision with respect to time. - [Fact] - public void should_correctly_persist_the_commit_stamp() - { - var difference = _persisted!.CommitStamp.Subtract(_now); - difference.Days.Should().Be(0); - difference.Hours.Should().Be(0); - difference.Minutes.Should().Be(0); - difference.Should().BeLessThanOrEqualTo(TimeSpan.FromSeconds(1)); - } - - [Fact] - public void should_correctly_persist_the_headers() - { - _persisted!.Headers.Count.Should().Be(_attempt!.Headers.Count); - } - - [Fact] - public void should_correctly_persist_the_events() - { - _persisted!.Events.Count.Should().Be(_attempt!.Events.Count); - } - - [Fact] - public async Task should_cause_the_stream_to_be_found_in_the_list_of_streams_to_snapshot() - { - var observer = new StreamHeadObserver(); - await Persistence.GetStreamsToSnapshotAsync(1, observer, CancellationToken.None); - observer.StreamHeads - .FirstOrDefault(x => x.StreamId == _streamId).Should().NotBeNull(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_a_given_revision : PersistenceEngineConcernAsync - { - private const int LoadFromCommitContainingRevision = 3; - private const int UpToCommitWithContainingRevision = 5; - private ICommit[]? _committed; - private ICommit? _oldest, _oldest2, _oldest3; - private string? _streamId; - - protected override async Task ContextAsync() - { - _oldest = await Persistence.CommitSingleAsync(); // 2 events, revision 1-2 - _oldest2 = await Persistence.CommitNextAsync(_oldest!); // 2 events, revision 3-4 - _oldest3 = await Persistence.CommitNextAsync(_oldest2!); // 2 events, revision 5-6 - await Persistence.CommitNextAsync(_oldest3!); // 2 events, revision 7-8 - - _streamId = _oldest!.StreamId; - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_streamId!, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision, observer, CancellationToken.None); - _committed = observer.Commits.ToArray(); - } - - [Fact] - public void should_start_from_the_commit_which_contains_the_min_stream_revision_specified() - { - _committed![0].CommitId.Should().Be(_oldest2!.CommitId); // contains revision 3 - } - - [Fact] - public void should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified() - { - _committed!.Last().CommitId.Should().Be(_oldest3!.CommitId); // contains revision 5 - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_a_given_revision_to_commit_revision : PersistenceEngineConcernAsync - { - private const int LoadFromCommitContainingRevision = 3; - private const int UpToCommitWithContainingRevision = 6; - private ICommit[]? _committed; - private ICommit? _oldest, _oldest2, _oldest3; - private string? _streamId; - - protected override async Task ContextAsync() - { - _oldest = await Persistence.CommitSingleAsync(); // 2 events, revision 1-2 - _oldest2 = await Persistence.CommitNextAsync(_oldest!); // 2 events, revision 3-4 - _oldest3 = await Persistence.CommitNextAsync(_oldest2!); // 2 events, revision 5-6 - await Persistence.CommitNextAsync(_oldest3!); // 2 events, revision 7-8 - - _streamId = _oldest!.StreamId; - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_streamId!, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision, observer, CancellationToken.None); - _committed = observer.Commits.ToArray(); - } - - [Fact] - public void should_start_from_the_commit_which_contains_the_min_stream_revision_specified() - { - _committed![0].CommitId.Should().Be(_oldest2!.CommitId); // contains revision 3 - } - - [Fact] - public void should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified() - { - _committed!.Last().CommitId.Should().Be(_oldest3!.CommitId); // contains revision 6 - } - } - - public class when_observer_stops_reading_the_stream_after_2_commits : PersistenceEngineConcernAsync - { - private ICommit? _oldest, _oldest2, _oldest3; - private string? _streamId; - - protected override async Task ContextAsync() - { - _oldest = await Persistence.CommitSingleAsync(); // 2 events, revision 1-2 - _oldest2 = await Persistence.CommitNextAsync(_oldest!); // 2 events, revision 3-4 - _oldest3 = await Persistence.CommitNextAsync(_oldest2!); // 2 events, revision 5-6 - await Persistence.CommitNextAsync(_oldest3!); // 2 events, revision 7-8 - - _streamId = _oldest!.StreamId; - } - - [Fact] - public async Task ICommitEvents_GetFromAsync_stops_after_2_commits() - { - bool _observerCompleted = false; - var _committed = new List(); - var observer = new LambdaAsyncObserver( - onNextAsync: (c, _) => - { - if (_committed.Count <= 1) - { - _committed.Add(c); - return Task.FromResult(true); - } - // do not read more than 2 commits - return Task.FromResult(false); - }, - onCompletedAsync: (_) => - { - _observerCompleted = true; - return Task.CompletedTask; - }); - await Persistence.GetFromAsync(Bucket.Default, _streamId!, 0, int.MaxValue, observer, CancellationToken.None); - - _committed!.Count.Should().Be(2); - _observerCompleted.Should().BeTrue(); - } - - [Fact] - public async Task IPersistStreams_GetFromAsync_Checkpoint_stops_after_2_commits() - { - bool _observerCompleted = false; - var _committed = new List(); - var observer = new LambdaAsyncObserver( - onNextAsync: (c, _) => - { - if (_committed.Count <= 1) - { - _committed.Add(c); - return Task.FromResult(true); - } - // do not read more than 2 commits - return Task.FromResult(false); - }, - onCompletedAsync: (_) => - { - _observerCompleted = true; - return Task.CompletedTask; - }); - await Persistence.GetFromAsync(0, observer, CancellationToken.None); - - _committed!.Count.Should().Be(2); - _observerCompleted.Should().BeTrue(); - } - - [Fact] - public async Task IPersistStreams_GetFromToAsync_Checkpoint_stops_after_2_commits() - { - bool _observerCompleted = false; - var _committed = new List(); - var observer = new LambdaAsyncObserver( - onNextAsync: (c, _) => - { - if (_committed.Count <= 1) - { - _committed.Add(c); - return Task.FromResult(true); - } - // do not read more than 2 commits - return Task.FromResult(false); - }, - onCompletedAsync: (_) => - { - _observerCompleted = true; - return Task.CompletedTask; - }); - await Persistence.GetFromToAsync(0, long.MaxValue, observer, CancellationToken.None); - - _committed!.Count.Should().Be(2); - _observerCompleted.Should().BeTrue(); - } - - [Fact] - public async Task IPersistStreams_GetFromAsync_Bucket_Checkpoint_stops_after_2_commits() - { - bool _observerCompleted = false; - var _committed = new List(); - var observer = new LambdaAsyncObserver( - onNextAsync: (c, _) => - { - if (_committed.Count <= 1) - { - _committed.Add(c); - return Task.FromResult(true); - } - // do not read more than 2 commits - return Task.FromResult(false); - }, - onCompletedAsync: (_) => - { - _observerCompleted = true; - return Task.CompletedTask; - }); - await Persistence.GetFromAsync(Bucket.Default, 0, observer, CancellationToken.None); - - _committed!.Count.Should().Be(2); - _observerCompleted.Should().BeTrue(); - } - - [Fact] - public async Task IPersistStreams_GetFromToAsync_Bucket_Checkpoint_stops_after_2_commits() - { - bool _observerCompleted = false; - var _committed = new List(); - var observer = new LambdaAsyncObserver( - onNextAsync: (c, _) => - { - if (_committed.Count <= 1) - { - _committed.Add(c); - return Task.FromResult(true); - } - // do not read more than 2 commits - return Task.FromResult(false); - }, - onCompletedAsync: (_) => - { - _observerCompleted = true; - return Task.CompletedTask; - }); - await Persistence.GetFromToAsync(Bucket.Default, 0, long.MaxValue, observer, CancellationToken.None); - - _committed!.Count.Should().Be(2); - _observerCompleted.Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_a_stream_with_the_same_revision : PersistenceEngineConcernAsync - { - private CommitAttempt? _attemptWithSameRevision; - private Exception? _thrown; - - protected override async Task ContextAsync() - { - var commit = await Persistence.CommitSingleAsync(); - _attemptWithSameRevision = commit!.StreamId.BuildAttempt(); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attemptWithSameRevision!, CancellationToken.None)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - } - - // This test ensure the uniqueness of BucketId+StreamId+CommitSequence - // to avoid concurrency issues -#if MSTEST - [TestClass] -#endif - public class when_committing_a_stream_with_the_same_sequence : PersistenceEngineConcernAsync - { - private CommitAttempt? _attempt1, _attempt2; - private Exception? _thrown; - - protected override Task ContextAsync() - { - string streamId = Guid.NewGuid().ToString(); - _attempt1 = streamId.BuildAttempt(); - _attempt2 = new CommitAttempt( - _attempt1.BucketId, // <--- Same bucket - _attempt1.StreamId, // <--- Same stream it - _attempt1.StreamRevision + 10, - Guid.NewGuid(), - _attempt1.CommitSequence, // <--- Same commit seq - DateTime.UtcNow, - _attempt1.Headers, - [ - new EventMessage(){ Body = new ExtensionMethods.SomeDomainEvent {SomeProperty = "Test 3"}} - ] - ); - - return Persistence.CommitAsync(_attempt1, CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attempt2!, CancellationToken.None)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - } - - //TODO:This test looks exactly like the one above. What are we trying to prove? -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_overwrite_a_committed_sequence : PersistenceEngineConcernAsync - { - private CommitAttempt? _failedAttempt; - private Exception? _thrown; - - protected override async Task ContextAsync() - { - string streamId = Guid.NewGuid().ToString(); - CommitAttempt successfulAttempt = streamId.BuildAttempt(); - await Persistence.CommitAsync(successfulAttempt, CancellationToken.None); - _failedAttempt = streamId.BuildAttempt(); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_failedAttempt!, CancellationToken.None)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_persist_a_commit_twice : PersistenceEngineConcernAsync - { - private CommitAttempt? _attemptTwice; - private Exception? _thrown; - - protected override async Task ContextAsync() - { - var commit = await Persistence.CommitSingleAsync(); - _attemptTwice = new CommitAttempt( - commit!.BucketId, - commit.StreamId, - commit.StreamRevision, - commit.CommitId, - commit.CommitSequence, - commit.CommitStamp, - commit.Headers.ToDictionary(k => k.Key, v => v.Value), - commit.Events.ToArray()); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attemptTwice!, CancellationToken.None)); - } - - [Fact] - public void should_throw_a_DuplicateCommitException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_persist_a_commitId_twice_on_same_stream : PersistenceEngineConcernAsync - { - private CommitAttempt? _attemptTwice; - private Exception? _thrown; - - protected override async Task ContextAsync() - { - var commit = await Persistence.CommitSingleAsync(); - _attemptTwice = new CommitAttempt( - commit!.BucketId, - commit.StreamId, - commit.StreamRevision + 1, - commit.CommitId, - commit.CommitSequence + 1, - commit.CommitStamp, - commit.Headers.ToDictionary(k => k.Key, v => v.Value), - commit.Events.ToArray() - ); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attemptTwice!, CancellationToken.None)); - } - - [Fact] - public void should_throw_a_DuplicateCommitException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_more_events_than_the_configured_page_size : PersistenceEngineConcernAsync - { - private CommitAttempt[]? _committed; - private ICommit[]? _loaded; - private string? _streamId; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - _committed = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 2, _streamId)).ToArray(); - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_streamId!, 0, int.MaxValue, observer, CancellationToken.None); - _loaded = observer.Commits.ToArray(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted() - { - _loaded!.Length.Should().Be(_committed!.Length); - } - - [Fact] - public void should_load_the_same_commits_which_have_been_persisted() - { - _committed! - .All(commit => _loaded!.SingleOrDefault(loaded => loaded.CommitId == commit.CommitId) != null) - .Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_saving_a_snapshot : PersistenceEngineConcernAsync - { - private bool _added; - private Snapshot? _snapshot; - private string? _streamId; - - protected override Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - _snapshot = new Snapshot(_streamId, 1, "Snapshot"); - return Persistence.CommitSingleAsync(_streamId); - } - - protected override async Task BecauseAsync() - { - _added = await Persistence.AddSnapshotAsync(_snapshot!, CancellationToken.None); - } - - [Fact] - public void should_indicate_the_snapshot_was_added() - { - _added.Should().BeTrue(); - } - - [Fact] - public async Task should_be_able_to_retrieve_the_snapshot() - { - (await Persistence.GetSnapshotAsync(_streamId!, _snapshot!.StreamRevision, CancellationToken.None)).Should().NotBeNull(); - } - } - - /// - /// having multiple snapshots for the same tuple: bucketId, streamId, streamRevision - /// should not be allowed, the resulting behavior should be ignoring or updating the - /// snapshot, that was the original design (it's up to the driver decide what to do) - /// this behavior can be changed in a future implementation. - /// -#if MSTEST - [TestClass] -#endif - public class when_adding_multiple_snapshots_for_same_bucketId_streamId_streamRevision : PersistenceEngineConcernAsync - { - private bool _added; - private Snapshot? _snapshot; - private Snapshot? _updatedSnapshot; - private string? _streamId; - - private Exception? _thrown; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - _snapshot = new Snapshot(_streamId, 1, "Snapshot"); - await Persistence.CommitSingleAsync(_streamId); - - await Persistence.AddSnapshotAsync(_snapshot, CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - _updatedSnapshot = new Snapshot(_streamId!, 1, "Updated Snapshot"); - _thrown = await Catch.ExceptionAsync(async () => _added = await Persistence.AddSnapshotAsync(_updatedSnapshot, CancellationToken.None)); - } - - [Fact] - public void should_not_raise_exception() - { - _thrown.Should().BeNull(); - } - - [Fact] - public async Task should_be_able_to_retrieve_the_correct_snapshot_original_or_updated_depends_on_driver_implementation() - { - var snapshot = await Persistence.GetSnapshotAsync(_streamId!, _snapshot!.StreamRevision, CancellationToken.None); - snapshot.Should().NotBeNull(); - if (_added) - { - snapshot!.Payload.Should().Be(_updatedSnapshot!.Payload, "The snapshot was added, I expected to get the most updated version"); - } - else - { - snapshot!.Payload.Should().Be(_snapshot.Payload, "The snapshot was not added, I expected to get the original version"); - } - } - } - -#if MSTEST - [TestClass] -#endif - public class when_retrieving_a_snapshot : PersistenceEngineConcernAsync - { - private Snapshot? _correct; - private ISnapshot? _snapshot; - private string? _streamId; - private Snapshot? _tooFarForward; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - var commit1 = await Persistence.CommitSingleAsync(_streamId); // rev 1-2 - var commit2 = await Persistence.CommitNextAsync(commit1!); // rev 3-4 - await Persistence.CommitNextAsync(commit2!); // rev 5-6 - - await Persistence.AddSnapshotAsync(new Snapshot(_streamId, 1, string.Empty), CancellationToken.None); //Too far back - _correct = new Snapshot(_streamId, 3, "Snapshot"); - await Persistence.AddSnapshotAsync(_correct, CancellationToken.None); - _tooFarForward = new Snapshot(_streamId, 5, string.Empty); - await Persistence.AddSnapshotAsync(_tooFarForward, CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - _snapshot = await Persistence.GetSnapshotAsync(_streamId!, _tooFarForward!.StreamRevision - 1, CancellationToken.None); - } - - [Fact] - public void should_load_the_most_recent_prior_snapshot() - { - _snapshot!.StreamRevision.Should().Be(_correct!.StreamRevision); - } - - [Fact] - public void should_have_the_correct_snapshot_payload() - { - _snapshot!.Payload.Should().Be(_correct!.Payload); - } - - [Fact] - public void should_have_the_correct_stream_id() - { - _snapshot!.StreamId.Should().Be(_correct!.StreamId); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_a_snapshot_has_been_added_to_the_most_recent_commit_of_a_stream : PersistenceEngineConcernAsync - { - private const string SnapshotData = "snapshot"; - private ICommit? _newest; - private ICommit? _oldest, _oldest2; - private string? _streamId; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - _oldest = await Persistence.CommitSingleAsync(_streamId); - _oldest2 = await Persistence.CommitNextAsync(_oldest!); - _newest = await Persistence.CommitNextAsync(_oldest2!); - } - - protected override Task BecauseAsync() - { - return Persistence.AddSnapshotAsync(new Snapshot(_streamId!, _newest!.StreamRevision, SnapshotData), CancellationToken.None); - } - - [Fact] - public async Task should_no_longer_find_the_stream_in_the_set_of_streams_to_be_snapshot() - { - var observer = new StreamHeadObserver(); - await Persistence.GetStreamsToSnapshotAsync(1, observer, CancellationToken.None); - observer.StreamHeads - .Any(x => x.StreamId == _streamId).Should().BeFalse(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_commit_after_a_snapshot : PersistenceEngineConcernAsync - { - private const int WithinThreshold = 2; - private const int OverThreshold = 3; - private const string SnapshotData = "snapshot"; - private ICommit? _oldest, _oldest2; - private string? _streamId; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - _oldest = await Persistence.CommitSingleAsync(_streamId); - _oldest2 = await Persistence.CommitNextAsync(_oldest!); - await Persistence.AddSnapshotAsync(new Snapshot(_streamId, _oldest2!.StreamRevision, SnapshotData), CancellationToken.None); - } - - protected override Task BecauseAsync() - { - return Persistence.CommitAsync(_oldest2!.BuildNextAttempt(), CancellationToken.None); - } - - // Because Raven and Mongo update the stream head asynchronously, this test will occasionally fail - [Fact] - public async Task should_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_within_the_threshold() - { - var observer = new StreamHeadObserver(); - await Persistence.GetStreamsToSnapshotAsync(WithinThreshold, observer, CancellationToken.None); - observer.StreamHeads - .FirstOrDefault(x => x.StreamId == _streamId).Should().NotBeNull(); - } - - [Fact] - public async Task should_not_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_over_the_threshold() - { - var observer = new StreamHeadObserver(); - await Persistence.GetStreamsToSnapshotAsync(OverThreshold, observer, CancellationToken.None); - observer.StreamHeads - .Any(x => x.StreamId == _streamId).Should().BeFalse(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_commits_from_a_particular_point_in_time : PersistenceEngineConcernAsync - { - private ICommit[]? _committed; - private CommitAttempt? _first; - private DateTime _now; - private ICommit? _second; - private string? _streamId; - private ICommit? _third; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - - _now = SystemTime.UtcNow.AddYears(1); - _first = _streamId.BuildAttempt(_now.AddSeconds(1)); - await Persistence.CommitAsync(_first, CancellationToken.None); - - _second = await Persistence.CommitNextAsync(_first); - _third = await Persistence.CommitNextAsync(_second!); - await Persistence.CommitNextAsync(_third!); - } - - protected override void Because() - { - _committed = Persistence.GetFrom(Bucket.Default, _now).ToArray(); - } - - [Fact] - public void should_return_all_commits_on_or_after_the_point_in_time_specified() - { - _committed!.Length.Should().Be(4); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_from_a_particular_point_in_time : PersistenceEngineConcernAsync - { - private CommitAttempt[]? _committed; - private ICommit[]? _loaded; - private DateTime _start; - - protected override async Task ContextAsync() - { - _start = SystemTime.UtcNow; - // Due to loss in precision in various storage engines, we're rounding down to the - // nearest second to ensure include all commits from the 'start'. - _start = _start.AddSeconds(-1); - _committed = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 2)).ToArray(); - } - - protected override void Because() - { - _loaded = Persistence.GetFrom(Bucket.Default, _start).ToArray(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted() - { - _loaded!.Length.Should().Be(_committed!.Length); - } - - [Fact] - public void should_load_the_same_commits_which_have_been_persisted() - { - _committed! - .All(commit => _loaded!.SingleOrDefault(loaded => loaded.CommitId == commit.CommitId) != null) - .Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_from_a_particular_checkpoint : PersistenceEngineConcernAsync - { - private List? _committed; - private List? _loaded; - private const int checkPoint = 2; - - protected override async Task ContextAsync() - { - _committed = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1)).Select(c => c.CommitId).ToList(); - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(checkPoint, observer, CancellationToken.None).ConfigureAwait(false); - _loaded = observer.Commits.Select(c => c.CommitId).ToList(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint() - { - _loaded!.Count.Should().Be(_committed!.Count - checkPoint); - } - - [Fact] - public void should_load_only_the_commits_starting_from_the_checkpoint() - { - _committed!.Skip(checkPoint).All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_of_a_bucket_from_a_particular_checkpoint : PersistenceEngineConcernAsync - { - private List? _committedOnBucket1; - private List? _committedOnBucket2; - private List? _loaded; - private const int checkPoint = 2; - - protected override async Task ContextAsync() - { - _committedOnBucket1 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "b1")).Select(c => c.CommitId).ToList(); - _committedOnBucket2 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "b2")).Select(c => c.CommitId).ToList(); - _committedOnBucket1.AddRange((await Persistence.CommitManyAsync(4, null, "b1")).Select(c => c.CommitId)); - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync("b1", checkPoint, observer, CancellationToken.None).ConfigureAwait(false); - _loaded = observer.Commits.Select(c => c.CommitId).ToList(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint() - { - _loaded!.Count.Should().Be(_committedOnBucket1!.Count - checkPoint); - } - - [Fact] - public void should_load_only_the_commits_on_bucket1_starting_from_the_checkpoint() - { - _committedOnBucket1!.Skip(checkPoint).All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection - } - - [Fact] - public void should_not_load_the_commits_from_bucket2() - { - _committedOnBucket2!.All(x => !_loaded!.Contains(x)).Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_from_a_particular_checkpoint_to_a_checkpoint : PersistenceEngineConcernAsync - { - private readonly List _committed = []; - private List? _loaded; - private const int startCheckpoint = 2; - private int endCheckpoint; - - protected override async Task ContextAsync() - { - var committedOnBucket1 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, Bucket.Default)).Select(c => c.CommitId).ToList(); - var committedOnBucket2 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "Bucket1")).Select(c => c.CommitId).ToList(); - _committed.AddRange(committedOnBucket1); - _committed.AddRange(committedOnBucket2); - endCheckpoint = (2 * (ConfiguredPageSizeForTesting + 1)) - 1; - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromToAsync(startCheckpoint, endCheckpoint, observer, CancellationToken.None).ConfigureAwait(false); - _loaded = observer.Commits.Select(c => c.CommitId).ToList(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint_to_the_checkpoint() - { - _loaded!.Count.Should().Be(endCheckpoint - startCheckpoint); - } - - [Fact] - public void should_load_only_the_commits_starting_from_the_checkpoint_to_the_checkpoint() - { - _committed - .Skip(startCheckpoint) - .Take(_committed.Count - startCheckpoint - 1) - //.Take(endCheckpoint - startCheckpoint) - .All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_of_a_bucket_from_a_particular_checkpoint_to_a_checkpoint : PersistenceEngineConcernAsync - { - private List? _committedOnBucket1; - private List? _committedOnBucket2; - private List? _loaded; - private const int startCheckpoint = 2; - private int endCheckpoint; - - protected override async Task ContextAsync() - { - _committedOnBucket1 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "b1")).Select(c => c.CommitId).ToList(); - _committedOnBucket2 = (await Persistence.CommitManyAsync(ConfiguredPageSizeForTesting + 1, null, "b2")).Select(c => c.CommitId).ToList(); - _committedOnBucket1.AddRange((await Persistence.CommitManyAsync(4, null, "b1")).Select(c => c.CommitId)); - endCheckpoint = ((2 * (ConfiguredPageSizeForTesting + 1)) + 4) - 1; - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromToAsync("b1", startCheckpoint, endCheckpoint, observer, CancellationToken.None).ConfigureAwait(false); - _loaded = observer.Commits.Select(c => c.CommitId).ToList(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint_to_the_checkpoint() - { - _loaded!.Count.Should().Be(_committedOnBucket1!.Count - startCheckpoint - 1); - } - - [Fact] - public void should_load_only_the_commits_on_bucket1_starting_from_the_checkpoint_to_the_checkpoint() - { - _committedOnBucket1! - .Skip(startCheckpoint) - .Take(_committedOnBucket1!.Count - startCheckpoint - 1) - .All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection - } - - [Fact] - public void should_not_load_the_commits_from_bucket2() - { - _committedOnBucket2!.All(x => !_loaded!.Contains(x)).Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_commits_from_the_year_1_AD : PersistenceEngineConcernAsync - { - private Exception? _thrown; - - protected override async Task BecauseAsync() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - _thrown = await Catch.ExceptionAsync(() => Persistence.GetFromAsync(Bucket.Default, 0, new CommitStreamObserver(), CancellationToken.None)); - } - - [Fact] - public void should_NOT_throw_an_exception() - { - _thrown.Should().BeNull(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_all_commits : PersistenceEngineConcernAsync - { - protected override Task ContextAsync() - { - return Persistence.CommitSingleAsync(); - } - - protected override Task BecauseAsync() - { - return Persistence.PurgeAsync(CancellationToken.None); - } - - [Fact] - public async Task should_not_find_any_commits_stored() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(Bucket.Default, 0, observer, CancellationToken.None).ConfigureAwait(false); - observer.Commits.Count.Should().Be(0); - } - - [Fact] - public async Task should_not_find_any_streams_to_snapshot() - { - var observer = new StreamHeadObserver(); - await Persistence.GetStreamsToSnapshotAsync(0, observer, CancellationToken.None); - observer.StreamHeads - .Count.Should().Be(0); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_invoking_after_disposal : PersistenceEngineConcernAsync - { - private Exception? _thrown; - - protected override void Context() - { - Persistence.Dispose(); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Persistence.CommitSingleAsync()); - } - - [Fact] - public void should_throw_an_ObjectDisposedException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_a_stream_with_the_same_id_as_a_stream_same_bucket : PersistenceEngineConcernAsync - { - private string? _streamId; - private static Exception? _thrown; - - protected override Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - return Persistence.CommitAsync(_streamId.BuildAttempt(), CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_streamId!.BuildAttempt(), CancellationToken.None)); - } - - [Fact] - public void should_throw() - { - _thrown.Should().NotBeNull(); - } - - [Fact] - public void should_be_duplicate_commit_exception() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_a_stream_with_the_same_id_as_a_stream_in_another_bucket : PersistenceEngineConcernAsync - { - private const string _bucketAId = "a"; - private const string _bucketBId = "b"; - private string? _streamId; - private static CommitAttempt? _attemptForBucketB; - private static Exception? _thrown; - private DateTime _attemptACommitStamp; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - DateTime now = SystemTime.UtcNow; - await Persistence.CommitAsync(_streamId.BuildAttempt(now, _bucketAId), CancellationToken.None); - - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_bucketAId, _streamId, 0, int.MaxValue, observer, CancellationToken.None); - _attemptACommitStamp = observer.Commits[0].CommitStamp; - - _attemptForBucketB = _streamId.BuildAttempt(now.Subtract(TimeSpan.FromDays(1)), _bucketBId); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Persistence.CommitAsync(_attemptForBucketB!, CancellationToken.None)); - } - - [Fact] - public void should_succeed() - { - _thrown.Should().BeNull(); - } - - [Fact] - public async Task should_persist_to_the_correct_bucket() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_bucketBId, _streamId!, 0, int.MaxValue, observer, CancellationToken.None); - var stream = observer.Commits; - - stream.Should().NotBeNull(); - stream.Count.Should().Be(1); - } - - [Fact] - public async Task should_not_affect_the_stream_from_the_other_bucket() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_bucketAId, _streamId!, 0, int.MaxValue, observer, CancellationToken.None); - var stream = observer.Commits; - - stream.Should().NotBeNull(); - stream.Count.Should().Be(1); - stream[0].CommitStamp.Should().Be(_attemptACommitStamp); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_saving_a_snapshot_for_a_stream_with_the_same_id_as_a_stream_in_another_bucket : PersistenceEngineConcernAsync - { - private const string _bucketAId = "a"; - private const string _bucketBId = "b"; - - private string? _streamId; - - private static Snapshot? _snapshot; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - _snapshot = new Snapshot(_bucketBId, _streamId, 1, "Snapshot"); - await Persistence.CommitAsync(_streamId.BuildAttempt(bucketId: _bucketAId), CancellationToken.None); - await Persistence.CommitAsync(_streamId.BuildAttempt(bucketId: _bucketBId), CancellationToken.None); - } - - protected override Task BecauseAsync() - { - return Persistence.AddSnapshotAsync(_snapshot!, CancellationToken.None); - } - - [Fact] - public async Task should_affect_snapshots_from_another_bucket() - { - (await Persistence.GetSnapshotAsync(_bucketAId, _streamId!, _snapshot!.StreamRevision, CancellationToken.None)).Should().BeNull(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_commits_from_a_particular_point_in_time_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcernAsync - { - private const string _bucketAId = "a"; - private const string _bucketBId = "b"; - - private static DateTime _now; - private static ICommit[]? _returnedCommits; - private CommitAttempt? _commitToBucketB; - - protected override async Task ContextAsync() - { - _now = SystemTime.UtcNow.AddYears(1); - - var commitToBucketA = Guid.NewGuid().ToString().BuildAttempt(_now.AddSeconds(1), _bucketAId); - - await Persistence.CommitAsync(commitToBucketA, CancellationToken.None); - commitToBucketA = commitToBucketA.BuildNextAttempt(); - await Persistence.CommitAsync(commitToBucketA, CancellationToken.None); - commitToBucketA = commitToBucketA.BuildNextAttempt(); - await Persistence.CommitAsync(commitToBucketA, CancellationToken.None); - await Persistence.CommitAsync(commitToBucketA.BuildNextAttempt(), CancellationToken.None); - - _commitToBucketB = Guid.NewGuid().ToString().BuildAttempt(_now.AddSeconds(1), _bucketBId); - - await Persistence.CommitAsync(_commitToBucketB, CancellationToken.None); - } - - protected override void Because() - { - _returnedCommits = Persistence.GetFrom(_bucketAId, _now).ToArray(); - } - - [Fact] - public void should_not_return_commits_from_other_buckets() - { - _returnedCommits!.Any(c => c.CommitId.Equals(_commitToBucketB!.CommitId)).Should().BeFalse(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_getting_all_commits_since_checkpoint_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcernAsync - { - private ICommit[]? _commits; - - protected override async Task ContextAsync() - { - const string bucketAId = "a"; - const string bucketBId = "b"; - await Persistence.CommitAsync(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketAId), CancellationToken.None); - await Persistence.CommitAsync(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketBId), CancellationToken.None); - await Persistence.CommitAsync(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketAId), CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); - _commits = observer.Commits.ToArray(); - } - - [Fact] - public void should_not_be_empty() - { - _commits.Should().NotBeEmpty(); - } - - [Fact] - public void should_be_in_order_by_checkpoint() - { - Int64 checkpoint = 0; - foreach (var commit in _commits!) - { - Int64 commitCheckpoint = commit.CheckpointToken; - commitCheckpoint.Should().BeGreaterThan(checkpoint); - checkpoint = commit.CheckpointToken; - } - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_all_commits_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcernAsync - { - private const string _bucketAId = "a"; - private const string _bucketBId = "b"; - - private string? _streamId; - - protected override async Task ContextAsync() - { - _streamId = Guid.NewGuid().ToString(); - await Persistence.CommitAsync(_streamId.BuildAttempt(bucketId: _bucketAId), CancellationToken.None); - await Persistence.CommitAsync(_streamId.BuildAttempt(bucketId: _bucketBId), CancellationToken.None); - var _snapshotA = new Snapshot(bucketId: _bucketAId, _streamId, 1, "SnapshotA"); - await Persistence.AddSnapshotAsync(_snapshotA, CancellationToken.None); - var _snapshotB = new Snapshot(bucketId: _bucketBId, _streamId, 1, "SnapshotB"); - await Persistence.AddSnapshotAsync(_snapshotB, CancellationToken.None); - } - - protected override Task BecauseAsync() - { - return Persistence.PurgeAsync(CancellationToken.None); - } - - [Fact] - public async Task should_purge_all_commits_stored_in_bucket_a() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_bucketAId, 0, observer, CancellationToken.None).ConfigureAwait(false); - observer.Commits.Count.Should().Be(0); - } - - [Fact] - public async Task should_purge_all_commits_stored_in_bucket_b() - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(_bucketBId, 0, observer, CancellationToken.None).ConfigureAwait(false); - observer.Commits.Count.Should().Be(0); - } - - [Fact] - public async Task should_purge_all_streams_to_snapshot_in_bucket_a() - { - var observer = new StreamHeadObserver(); - await Persistence.GetStreamsToSnapshotAsync(_bucketAId, 0, observer, CancellationToken.None); - observer.StreamHeads.Count.Should().Be(0); - } - - [Fact] - public async Task should_purge_all_streams_to_snapshot_in_bucket_b() - { - var observer = new StreamHeadObserver(); - await Persistence.GetStreamsToSnapshotAsync(_bucketBId, 0, observer, CancellationToken.None); - observer.StreamHeads.Count.Should().Be(0); - } - } - - [Serializable] - public class TestEventForAsync - { - public String? S { get; set; } - } - -#if MSTEST - [TestClass] -#endif - public class when_calling_CommitChanges : PersistenceEngineConcern - { - private Guid? _commitId; - private ICommit? _persistedCommit; - private ICommit[]? _commits; - - protected override async Task BecauseAsync() - { - var eventStore = new OptimisticEventStore(Persistence, null, null); - using IEventStream stream = await eventStore.OpenStreamAsync(Guid.NewGuid()).ConfigureAwait(false); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " } }); - _commitId = Guid.NewGuid(); - _persistedCommit = await stream.CommitChangesAsync(_commitId.Value).ConfigureAwait(false); - - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); - _commits = observer.Commits.ToArray(); - } - - [Fact] - public void A_Commit_had_been_persisted() - { - _persistedCommit.Should().NotBeNull(); - _persistedCommit!.CommitId.Should().Be(_commitId!.Value); - _persistedCommit.CommitSequence.Should().Be(1); - _persistedCommit.Events.Count.Should().Be(1); - _persistedCommit.Events.Single().Body.Should().BeOfType(); - ((TestEvent)_persistedCommit.Events.Single().Body!).S.Should().Be("Hi "); - } - - [Fact] - public void Should_have_expected_number_of_commits() - { - _commits!.Length.Should().Be(1); - // if should have the right event - _commits[0].CommitId.Should().Be(_commitId!.Value); - _commits[0].Events.Count.Should().Be(1); - _commits[0].Events.Single().Body.Should().BeOfType(); - ((TestEvent)_commits[0].Events.Single().Body!).S.Should().Be("Hi "); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_gettingFromCheckpoint_amount_of_commits_exceeds_PageSize : PersistenceEngineConcernAsync - { - private ICommit[]? _commits; - private int _moreThanPageSize; - - protected override async Task BecauseAsync() - { - _moreThanPageSize = ConfiguredPageSizeForTesting + 1; - var eventStore = new OptimisticEventStore(Persistence, null, null); - for (int i = 0; i < _moreThanPageSize; i++) - { - using IEventStream stream = await eventStore.OpenStreamAsync(Guid.NewGuid()).ConfigureAwait(false); - stream.Add(new EventMessage { Body = new TestEventForAsync() { S = "Hi " + i } }); - await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); - } - - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); - _commits = observer.Commits.ToArray(); - } - - [Fact] - public void Should_have_expected_number_of_commits() - { - _commits!.Length.Should().Be(_moreThanPageSize); - } - } - - /// - /// - /// NEventStore.Persistence.Sql issue #54 - /// GetStreamsToSnapshot fails when number of streams exceeds PageSize. - /// - /// - /// Query implementation was wrong: checking how the query and the pagination were implemented - /// it was lacking to check for C.StreamId > @StreamId. - /// which is needed to page through the results (similar to @Coalesce in GetCommitsFromStartingRevision) - /// - /// -#if MSTEST - [TestClass] -#endif - public class when_getting_streams_to_snapshot_amount_exceeds_PageSize : PersistenceEngineConcernAsync - { - private int _moreThanPageSize; - private Guid _lastStreamId; - - protected override async Task BecauseAsync() - { - _moreThanPageSize = ConfiguredPageSizeForTesting + 1; - var eventStore = new OptimisticEventStore(Persistence, null, null); - for (int i = 0; i < _moreThanPageSize; i++) - { - _lastStreamId = Guid.NewGuid(); - using IEventStream stream = await eventStore.OpenStreamAsync(_lastStreamId).ConfigureAwait(false); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " + i } }); - await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " + i } }); - await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " + i } }); - await stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None).ConfigureAwait(false); - } - } - - [Fact] - public void GetStreamsToSnapshot_does_not_crash_and_returns_all_the_streams() - { - Assert.DoesNotThrowAsync(async () => - { - var observer = new StreamHeadObserver(); - await Persistence.GetStreamsToSnapshotAsync(0, observer, CancellationToken.None).ConfigureAwait(false); - observer.StreamHeads.Count.Should().Be(_moreThanPageSize); - }); - } - - [Fact] - public void GetFrom_does_not_crash_and_returns_all_the_streams() - { - Assert.DoesNotThrowAsync(async () => - { - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(Bucket.Default, _lastStreamId.ToString(), 0, int.MaxValue, observer, CancellationToken.None).ConfigureAwait(false); - observer.Commits.Count.Should().Be(3); - }); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_a_payload_is_large : PersistenceEngineConcernAsync - { - [Fact] - public async Task can_commit() - { - const int bodyLength = 100000; - var attempt = new CommitAttempt( - Bucket.Default, - Guid.NewGuid().ToString(), - 1, - Guid.NewGuid(), - 1, - DateTime.UtcNow, - new Dictionary(), - [new EventMessage { Body = new string('a', bodyLength) }]); - await Persistence.CommitAsync(attempt, CancellationToken.None); - - var observer = new CommitStreamObserver(); - await Persistence.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); - ICommit commits = observer.Commits.Single(); - - commits.Events.Single().Body.ToString()!.Length.Should().Be(bodyLength); - } - } - - /// - /// We are adapting the tests to use 3 different frameworks: - /// - XUnit: the attached test runner does the job (fixture setup and cleanup) - /// - NUnit (.net core project) - /// - MSTest (.net core project) - /// - public abstract class PersistenceEngineConcernAsync : SpecificationBase -#if XUNIT - , IUseFixture -#endif -#if NUNIT || MSTEST - , IDisposable -#endif - { - protected PersistenceEngineFixtureAsync? Fixture { get; private set; } - - protected IPersistStreams Persistence - { - get { return Fixture!.Persistence!; } - } - - protected int ConfiguredPageSizeForTesting - { - get { return 2; } - } - - /// - /// Can be used by XUNIT to initialize the tests. - /// - public void SetFixture(PersistenceEngineFixtureAsync data) - { - Fixture = data; - Fixture.Initialize(ConfiguredPageSizeForTesting); - } - -#if NUNIT || MSTEST - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - Fixture?.Dispose(); - } - - /// - /// - /// This code was meant to be run right before every test in the fixture to give time - /// to do further initialization before the PersistenceEngineFixture was created. - /// Unfortunately the 3 frameworks - /// have very different ways of doing this: - /// - NUnit: TestFixtureSetUp - /// - MSTest: ClassInitialize (not inherited, will be ignored if defined on a base class) - /// - xUnit: IUseFixture + SetFixture - /// We need a way to also have some configuration before the PersistenceEngineFixture is created. - /// - /// - /// We've decided to use the test constructor to do the job, it's your responsibility to guarantee - /// One time initialization (for anything that need it, if you have multiple tests on a fixture) - /// depending on the framework you are using. - /// - /// - /// quick workaround: - /// - change the parameters of the "Fixture" properties. - /// - call the 'Reinitialize()' method can be called to rerun the initialization after we changed the configuration - /// in the constructor. - /// or - /// - call the SetFixture() to reinitialize everything. - /// - /// - protected PersistenceEngineConcernAsync() : this(new PersistenceEngineFixtureAsync()) - { } - - protected PersistenceEngineConcernAsync(PersistenceEngineFixtureAsync fixture) - { - SetFixture(fixture); - } -#endif - } - - public partial class PersistenceEngineFixtureAsync : IDisposable - { - public IPersistStreams? Persistence { get; private set; } - - private readonly Func _createPersistence; - -#if NET462 - private bool _tracking = false; - private string _trackingInstanceName; - - /// - /// Automatic Performance Counters and tracking was disabled for full - /// framework tests because their initialization - /// can fail when the tests run on build machines (like AppVeyor and similar). - /// You can enable it back calling this function before - /// - /// - /// - public PersistenceEngineFixture TrackPerformanceInstance(string instanceName = "tests") - { - _trackingInstanceName = instanceName; - _tracking = true; - return this; - } -#endif - - public void Initialize(int pageSize) - { -#if NET462 - // performance counters cab be disabled for full framework tests because their initialization - // can fail when the tests run on build machines (like AppVeyor and similar) - if (_tracking) - { - Persistence = new NEventStore.Diagnostics.PerformanceCounterPersistenceEngine(_createPersistence(pageSize), _trackingInstanceName); - } - else - { - Persistence = _createPersistence(pageSize); - } -#else - Persistence = _createPersistence(pageSize); -#endif - Persistence.Initialize(); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (Persistence?.IsDisposed == false) - { - Persistence.Drop(); - Persistence.Dispose(); - } - } - } -} - -#pragma warning restore 169 // ReSharper disable InconsistentNaming -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Transactions.cs b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Transactions.cs deleted file mode 100644 index 2884ab731..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.Transactions.cs +++ /dev/null @@ -1,161 +0,0 @@ -#pragma warning disable 169 // ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable S101 // Types should be named in PascalCase - -namespace NEventStore.Persistence.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using NEventStore.Persistence.AcceptanceTests.BDD; - using FluentAssertions; -#if MSTEST - using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT - using NUnit.Framework; - using System.Threading.Tasks; - using System.Transactions; - using System.Threading; - using System.Globalization; -#endif -#if XUNIT - using Xunit; - using Xunit.Should; -#endif - - /* Transactions support must be investigated, it should be valid only for Databases that supports it (InMemoryPersistence will not). */ -#if MSTEST - [TestClass] -#endif - public class TransactionConcern : PersistenceEngineConcern - { - private ICommit[] _commits; - private const int Loop = 2; - private const int StreamsPerTransaction = 20; - - protected override void Because() - { - Parallel.For(0, Loop, i => - { - var eventStore = new OptimisticEventStore(Persistence, null); - using (var scope = new TransactionScope(TransactionScopeOption.Required, - new TransactionOptions { IsolationLevel = IsolationLevel.Serializable })) - { - int j; - for (j = 0; j < StreamsPerTransaction; j++) - { - using (var stream = eventStore.OpenStream(i.ToString() + "-" + j.ToString())) - { - for (int k = 0; k < 10; k++) - { - stream.Add(new EventMessage { Body = "body" + k }); - } - stream.CommitChanges(Guid.NewGuid()); - } - } - scope.Complete(); - } - }); - _commits = Persistence.GetFrom().ToArray(); - } - - [Fact] - public void Should_have_expected_number_of_commits() - { - _commits.Length.Should().Be(Loop * StreamsPerTransaction); - } - - [Fact] - public void ScopeCompleteAndSerializable() - { - Reinitialize(); - const int loop = 10; - using (var scope = new TransactionScope( - TransactionScopeOption.Required, - new TransactionOptions - { - IsolationLevel = IsolationLevel.Serializable - })) - { - Parallel.For(0, loop, i => - { - Console.WriteLine("Creating stream {0} on thread {1}", i, Thread.CurrentThread.ManagedThreadId); - var eventStore = new OptimisticEventStore(Persistence, null); - string streamId = i.ToString(CultureInfo.InvariantCulture); - using (var stream = eventStore.OpenStream(streamId)) - { - stream.Add(new EventMessage { Body = "body1" }); - stream.Add(new EventMessage { Body = "body2" }); - stream.CommitChanges(Guid.NewGuid()); - } - }); - scope.Complete(); - } - ICommit[] commits = Persistence.GetFrom(0).ToArray(); - commits.Length.Should().Be(loop); - } - - [Fact] - public void ScopeNotCompleteAndReadCommitted() - { - Reinitialize(); - const int loop = 10; - using (new TransactionScope( - TransactionScopeOption.Required, - new TransactionOptions - { - IsolationLevel = IsolationLevel.ReadCommitted - })) - { - Parallel.For(0, loop, i => - { - Console.WriteLine(@"Creating stream {0} on thread {1}", i, Thread.CurrentThread.ManagedThreadId); - var eventStore = new OptimisticEventStore(Persistence, null); - string streamId = i.ToString(CultureInfo.InvariantCulture); - using (var stream = eventStore.OpenStream(streamId)) - { - stream.Add(new EventMessage { Body = "body1" }); - stream.Add(new EventMessage { Body = "body2" }); - stream.CommitChanges(Guid.NewGuid()); - } - }); - } - ICommit[] commits = Persistence.GetFrom(0).ToArray(); - commits.Length.Should().Be(0); - } - - [Fact] - public void ScopeNotCompleteAndSerializable() - { - Reinitialize(); - const int loop = 10; - using (new TransactionScope( - TransactionScopeOption.Required, - new TransactionOptions - { - IsolationLevel = IsolationLevel.ReadCommitted - })) - { - Parallel.For(0, loop, i => - { - Console.WriteLine(@"Creating stream {0} on thread {1}", i, Thread.CurrentThread.ManagedThreadId); - var eventStore = new OptimisticEventStore(Persistence, null); - string streamId = i.ToString(CultureInfo.InvariantCulture); - using (var stream = eventStore.OpenStream(streamId)) - { - stream.Add(new EventMessage { Body = "body1" }); - stream.Add(new EventMessage { Body = "body2" }); - stream.CommitChanges(Guid.NewGuid()); - } - }); - } - ICommit[] commits = Persistence.GetFrom(0).ToArray(); - commits.Length.Should().Be(0); - } - } -} - -#pragma warning restore S101 // Types should be named in PascalCase -#pragma warning restore 169 // ReSharper disable InconsistentNaming -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.cs b/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.cs deleted file mode 100644 index b8dc5fa55..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/PersistenceTests.cs +++ /dev/null @@ -1,1475 +0,0 @@ - -#pragma warning disable 169 // ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Assert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert; -#endif -#if NUNIT -using NUnit.Framework; -using Assert = NUnit.Framework.Assert; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.Persistence.AcceptanceTests -{ -#if MSTEST - [TestClass] -#endif - public class when_a_commit_header_has_a_name_that_contains_a_period : PersistenceEngineConcern - { - private ICommit? _persisted; - private string? _streamId; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - var attempt = new CommitAttempt(_streamId, - 2, - Guid.NewGuid(), - 1, - DateTime.UtcNow, - new Dictionary { { "key.1", "value" } }, - [new EventMessage { Body = new ExtensionMethods.SomeDomainEvent { SomeProperty = "Test" } }]); - Persistence.Commit(attempt); - } - - protected override void Because() - { - _persisted = Persistence.GetFrom(_streamId!, 0, int.MaxValue).First(); - } - - [Fact] - public void should_correctly_deserialize_headers() - { - _persisted!.Headers.Keys.Should().Contain("key.1"); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_a_commit_is_successfully_persisted : PersistenceEngineConcern - { - private CommitAttempt? _attempt; - private DateTime _now; - private ICommit? _persisted; - private string? _streamId; - - protected override void Context() - { - _now = SystemTime.UtcNow.AddYears(1); - _streamId = Guid.NewGuid().ToString(); - _attempt = _streamId.BuildAttempt(_now); - - Persistence.Commit(_attempt); - } - - protected override void Because() - { - _persisted = Persistence.GetFrom(_streamId!, 0, int.MaxValue).First(); - } - - [Fact] - public void should_correctly_persist_the_stream_identifier() - { - _persisted!.StreamId.Should().Be(_attempt!.StreamId); - } - - [Fact] - public void should_correctly_persist_the_stream_stream_revision() - { - _persisted!.StreamRevision.Should().Be(_attempt!.StreamRevision); - } - - [Fact] - public void should_correctly_persist_the_commit_identifier() - { - _persisted!.CommitId.Should().Be(_attempt!.CommitId); - } - - [Fact] - public void should_correctly_persist_the_commit_sequence() - { - _persisted!.CommitSequence.Should().Be(_attempt!.CommitSequence); - } - - // persistence engines have varying levels of precision with respect to time. - [Fact] - public void should_correctly_persist_the_commit_stamp() - { - var difference = _persisted!.CommitStamp.Subtract(_now); - difference.Days.Should().Be(0); - difference.Hours.Should().Be(0); - difference.Minutes.Should().Be(0); - difference.Should().BeLessThanOrEqualTo(TimeSpan.FromSeconds(1)); - } - - [Fact] - public void should_correctly_persist_the_headers() - { - _persisted!.Headers.Count.Should().Be(_attempt!.Headers.Count); - } - - [Fact] - public void should_correctly_persist_the_events() - { - _persisted!.Events.Count.Should().Be(_attempt!.Events.Count); - } - - [Fact] - public void should_cause_the_stream_to_be_found_in_the_list_of_streams_to_snapshot() - { - Persistence.GetStreamsToSnapshot(1).FirstOrDefault(x => x.StreamId == _streamId).Should().NotBeNull(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_a_given_revision : PersistenceEngineConcern - { - private const int LoadFromCommitContainingRevision = 3; - private const int UpToCommitWithContainingRevision = 5; - private ICommit[]? _committed; - private ICommit? _oldest, _oldest2, _oldest3; - private string? _streamId; - - protected override void Context() - { - _oldest = Persistence.CommitSingle(); // 2 events, revision 1-2 - _oldest2 = Persistence.CommitNext(_oldest!); // 2 events, revision 3-4 - _oldest3 = Persistence.CommitNext(_oldest2!); // 2 events, revision 5-6 - Persistence.CommitNext(_oldest3!); // 2 events, revision 7-8 - - _streamId = _oldest!.StreamId; - } - - protected override void Because() - { - _committed = Persistence.GetFrom(_streamId!, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision).ToArray(); - } - - [Fact] - public void should_start_from_the_commit_which_contains_the_min_stream_revision_specified() - { - _committed![0].CommitId.Should().Be(_oldest2!.CommitId); // contains revision 3 - } - - [Fact] - public void should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified() - { - _committed!.Last().CommitId.Should().Be(_oldest3!.CommitId); // contains revision 5 - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_a_given_revision_to_commit_revision : PersistenceEngineConcern - { - private const int LoadFromCommitContainingRevision = 3; - private const int UpToCommitWithContainingRevision = 6; - private ICommit[]? _committed; - private ICommit? _oldest, _oldest2, _oldest3; - private string? _streamId; - - protected override void Context() - { - _oldest = Persistence.CommitSingle(); // 2 events, revision 1-2 - _oldest2 = Persistence.CommitNext(_oldest!); // 2 events, revision 3-4 - _oldest3 = Persistence.CommitNext(_oldest2!); // 2 events, revision 5-6 - Persistence.CommitNext(_oldest3!); // 2 events, revision 7-8 - - _streamId = _oldest!.StreamId; - } - - protected override void Because() - { - _committed = Persistence.GetFrom(_streamId!, LoadFromCommitContainingRevision, UpToCommitWithContainingRevision).ToArray(); - } - - [Fact] - public void should_start_from_the_commit_which_contains_the_min_stream_revision_specified() - { - _committed![0].CommitId.Should().Be(_oldest2!.CommitId); // contains revision 3 - } - - [Fact] - public void should_read_up_to_the_commit_which_contains_the_max_stream_revision_specified() - { - _committed!.Last().CommitId.Should().Be(_oldest3!.CommitId); // contains revision 6 - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_a_stream_with_the_same_revision : PersistenceEngineConcern - { - private CommitAttempt? _attemptWithSameRevision; - private Exception? _thrown; - - protected override void Context() - { - var commit = Persistence.CommitSingle(); - _attemptWithSameRevision = commit!.StreamId.BuildAttempt(); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Persistence.Commit(_attemptWithSameRevision!)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - } - - // This test ensure the uniqueness of BucketId+StreamId+CommitSequence - // to avoid concurrency issues -#if MSTEST - [TestClass] -#endif - public class when_committing_a_stream_with_the_same_sequence : PersistenceEngineConcern - { - private CommitAttempt? _attempt1, _attempt2; - private Exception? _thrown; - - protected override void Context() - { - string streamId = Guid.NewGuid().ToString(); - _attempt1 = streamId.BuildAttempt(); - _attempt2 = new CommitAttempt( - _attempt1.BucketId, // <--- Same bucket - _attempt1.StreamId, // <--- Same stream it - _attempt1.StreamRevision + 10, - Guid.NewGuid(), - _attempt1.CommitSequence, // <--- Same commit seq - DateTime.UtcNow, - _attempt1.Headers, - [ - new EventMessage(){ Body = new ExtensionMethods.SomeDomainEvent {SomeProperty = "Test 3"}} - ] - ); - - Persistence.Commit(_attempt1); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Persistence.Commit(_attempt2!)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - } - - //TODO:This test looks exactly like the one above. What are we trying to prove? -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_overwrite_a_committed_sequence : PersistenceEngineConcern - { - private CommitAttempt? _failedAttempt; - private Exception? _thrown; - - protected override void Context() - { - string streamId = Guid.NewGuid().ToString(); - CommitAttempt successfulAttempt = streamId.BuildAttempt(); - Persistence.Commit(successfulAttempt); - _failedAttempt = streamId.BuildAttempt(); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Persistence.Commit(_failedAttempt!)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_persist_a_commit_twice : PersistenceEngineConcern - { - private CommitAttempt? _attemptTwice; - private Exception? _thrown; - - protected override void Context() - { - var commit = Persistence.CommitSingle(); - _attemptTwice = new CommitAttempt( - commit!.BucketId, - commit.StreamId, - commit.StreamRevision, - commit.CommitId, - commit.CommitSequence, - commit.CommitStamp, - commit.Headers.ToDictionary(k => k.Key, v => v.Value), - commit.Events.ToArray()); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Persistence.Commit(_attemptTwice!)); - } - - [Fact] - public void should_throw_a_DuplicateCommitException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_persist_a_commitId_twice_on_same_stream : PersistenceEngineConcern - { - private CommitAttempt? _attemptTwice; - private Exception? _thrown; - - protected override void Context() - { - var commit = Persistence.CommitSingle(); - _attemptTwice = new CommitAttempt( - commit!.BucketId, - commit.StreamId, - commit.StreamRevision + 1, - commit.CommitId, - commit.CommitSequence + 1, - commit.CommitStamp, - commit.Headers.ToDictionary(k => k.Key, v => v.Value), - commit.Events.ToArray() - ); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Persistence.Commit(_attemptTwice!)); - } - - [Fact] - public void should_throw_a_DuplicateCommitException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_more_events_than_the_configured_page_size : PersistenceEngineConcern - { - private CommitAttempt[]? _committed; - private ICommit[]? _loaded; - private string? _streamId; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - _committed = Persistence.CommitMany(ConfiguredPageSizeForTesting + 2, _streamId).ToArray(); - } - - protected override void Because() - { - _loaded = Persistence.GetFrom(_streamId!, 0, int.MaxValue).ToArray(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted() - { - _loaded!.Length.Should().Be(_committed!.Length); - } - - [Fact] - public void should_load_the_same_commits_which_have_been_persisted() - { - _committed! - .All(commit => _loaded!.SingleOrDefault(loaded => loaded.CommitId == commit.CommitId) != null) - .Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_saving_a_snapshot : PersistenceEngineConcern - { - private bool _added; - private Snapshot? _snapshot; - private string? _streamId; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - _snapshot = new Snapshot(_streamId, 1, "Snapshot"); - Persistence.CommitSingle(_streamId); - } - - protected override void Because() - { - _added = Persistence.AddSnapshot(_snapshot!); - } - - [Fact] - public void should_indicate_the_snapshot_was_added() - { - _added.Should().BeTrue(); - } - - [Fact] - public void should_be_able_to_retrieve_the_snapshot() - { - Persistence.GetSnapshot(_streamId!, _snapshot!.StreamRevision).Should().NotBeNull(); - } - } - - /// - /// having multiple snapshots for the same tuple: bucketId, streamId, streamRevision - /// should not be allowed, the resulting behavior should be ignoring or updating the - /// snapshot, that was the original design (it's up to the driver decide what to do) - /// this behavior can be changed in a future implementation. - /// -#if MSTEST - [TestClass] -#endif - public class when_adding_multiple_snapshots_for_same_bucketId_streamId_streamRevision : PersistenceEngineConcern - { - private bool _added; - private Snapshot? _snapshot; - private Snapshot? _updatedSnapshot; - private string? _streamId; - - private Exception? _thrown; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - _snapshot = new Snapshot(_streamId, 1, "Snapshot"); - Persistence.CommitSingle(_streamId); - - Persistence.AddSnapshot(_snapshot); - } - - protected override void Because() - { - _updatedSnapshot = new Snapshot(_streamId!, 1, "Updated Snapshot"); - _thrown = Catch.Exception(() => _added = Persistence.AddSnapshot(_updatedSnapshot)); - } - - [Fact] - public void should_not_raise_exception() - { - _thrown.Should().BeNull(); - } - - [Fact] - public void should_be_able_to_retrieve_the_correct_snapshot_original_or_updated_depends_on_driver_implementation() - { - var snapshot = Persistence.GetSnapshot(_streamId!, _snapshot!.StreamRevision); - snapshot.Should().NotBeNull(); - if (_added) - { - snapshot!.Payload.Should().Be(_updatedSnapshot!.Payload, "The snapshot was added, I expected to get the most updated version"); - } - else - { - snapshot!.Payload.Should().Be(_snapshot.Payload, "The snapshot was not added, I expected to get the original version"); - } - } - } - -#if MSTEST - [TestClass] -#endif - public class when_retrieving_a_snapshot : PersistenceEngineConcern - { - private Snapshot? _correct; - private ISnapshot? _snapshot; - private string? _streamId; - private Snapshot? _tooFarForward; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - var commit1 = Persistence.CommitSingle(_streamId); // rev 1-2 - var commit2 = Persistence.CommitNext(commit1!); // rev 3-4 - Persistence.CommitNext(commit2!); // rev 5-6 - - Persistence.AddSnapshot(new Snapshot(_streamId, 1, string.Empty)); //Too far back - _correct = new Snapshot(_streamId, 3, "Snapshot"); - Persistence.AddSnapshot(_correct); - _tooFarForward = new Snapshot(_streamId, 5, string.Empty); - Persistence.AddSnapshot(_tooFarForward); - } - - protected override void Because() - { - _snapshot = Persistence.GetSnapshot(_streamId!, _tooFarForward!.StreamRevision - 1); - } - - [Fact] - public void should_load_the_most_recent_prior_snapshot() - { - _snapshot!.StreamRevision.Should().Be(_correct!.StreamRevision); - } - - [Fact] - public void should_have_the_correct_snapshot_payload() - { - _snapshot!.Payload.Should().Be(_correct!.Payload); - } - - [Fact] - public void should_have_the_correct_stream_id() - { - _snapshot!.StreamId.Should().Be(_correct!.StreamId); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_a_snapshot_has_been_added_to_the_most_recent_commit_of_a_stream : PersistenceEngineConcern - { - private const string SnapshotData = "snapshot"; - private ICommit? _newest; - private ICommit? _oldest, _oldest2; - private string? _streamId; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - _oldest = Persistence.CommitSingle(_streamId); - _oldest2 = Persistence.CommitNext(_oldest!); - _newest = Persistence.CommitNext(_oldest2!); - } - - protected override void Because() - { - Persistence.AddSnapshot(new Snapshot(_streamId!, _newest!.StreamRevision, SnapshotData)); - } - - [Fact] - public void should_no_longer_find_the_stream_in_the_set_of_streams_to_be_snapshot() - { - Persistence.GetStreamsToSnapshot(1).Any(x => x.StreamId == _streamId).Should().BeFalse(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_commit_after_a_snapshot : PersistenceEngineConcern - { - private const int WithinThreshold = 2; - private const int OverThreshold = 3; - private const string SnapshotData = "snapshot"; - private ICommit? _oldest, _oldest2; - private string? _streamId; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - _oldest = Persistence.CommitSingle(_streamId); - _oldest2 = Persistence.CommitNext(_oldest!); - Persistence.AddSnapshot(new Snapshot(_streamId, _oldest2!.StreamRevision, SnapshotData)); - } - - protected override void Because() - { - Persistence.Commit(_oldest2!.BuildNextAttempt()); - } - - // Because Raven and Mongo update the stream head asynchronously, occasionally will fail this test - [Fact] - public void should_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_within_the_threshold() - { - Persistence.GetStreamsToSnapshot(WithinThreshold).FirstOrDefault(x => x.StreamId == _streamId).Should().NotBeNull(); - } - - [Fact] - public void should_not_find_the_stream_in_the_set_of_streams_to_be_snapshot_when_over_the_threshold() - { - Persistence.GetStreamsToSnapshot(OverThreshold).Any(x => x.StreamId == _streamId).Should().BeFalse(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_commits_from_a_particular_point_in_time : PersistenceEngineConcern - { - private ICommit[]? _committed; - private CommitAttempt? _first; - private DateTime _now; - private ICommit? _second; - private string? _streamId; - private ICommit? _third; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - - _now = SystemTime.UtcNow.AddYears(1); - _first = _streamId.BuildAttempt(_now.AddSeconds(1)); - Persistence.Commit(_first); - - _second = Persistence.CommitNext(_first); - _third = Persistence.CommitNext(_second!); - Persistence.CommitNext(_third!); - } - - protected override void Because() - { - _committed = Persistence.GetFrom(Bucket.Default, _now).ToArray(); - } - - [Fact] - public void should_return_all_commits_on_or_after_the_point_in_time_specified() - { - _committed!.Length.Should().Be(4); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_from_a_particular_point_in_time : PersistenceEngineConcern - { - private CommitAttempt[]? _committed; - private ICommit[]? _loaded; - private DateTime _start; - - protected override void Context() - { - _start = SystemTime.UtcNow; - // Due to loss in precision in various storage engines, we're rounding down to the - // nearest second to ensure include all commits from the 'start'. - _start = _start.AddSeconds(-1); - _committed = Persistence.CommitMany(ConfiguredPageSizeForTesting + 2).ToArray(); - } - - protected override void Because() - { - _loaded = Persistence.GetFrom(Bucket.Default, _start).ToArray(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted() - { - _loaded!.Length.Should().Be(_committed!.Length); - } - - [Fact] - public void should_load_the_same_commits_which_have_been_persisted() - { - _committed! - .All(commit => _loaded!.SingleOrDefault(loaded => loaded.CommitId == commit.CommitId) != null) - .Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_from_a_particular_checkpoint : PersistenceEngineConcern - { - private List? _committed; - private List? _loaded; - private const int checkPoint = 2; - - protected override void Context() - { - _committed = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1).Select(c => c.CommitId).ToList(); - } - - protected override void Because() - { - _loaded = Persistence.GetFrom(checkPoint).Select(c => c.CommitId).ToList(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint() - { - _loaded!.Count.Should().Be(_committed!.Count - checkPoint); - } - - [Fact] - public void should_load_only_the_commits_starting_from_the_checkpoint() - { - _committed!.Skip(checkPoint).All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_of_a_bucket_from_a_particular_checkpoint : PersistenceEngineConcern - { - private List? _committedOnBucket1; - private List? _committedOnBucket2; - private List? _loaded; - private const int checkPoint = 2; - - protected override void Context() - { - _committedOnBucket1 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "b1").Select(c => c.CommitId).ToList(); - _committedOnBucket2 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "b2").Select(c => c.CommitId).ToList(); - _committedOnBucket1.AddRange(Persistence.CommitMany(4, null, "b1").Select(c => c.CommitId)); - } - - protected override void Because() - { - _loaded = Persistence.GetFrom("b1", checkPoint).Select(c => c.CommitId).ToList(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint() - { - _loaded!.Count.Should().Be(_committedOnBucket1!.Count - checkPoint); - } - - [Fact] - public void should_load_only_the_commits_on_bucket1_starting_from_the_checkpoint() - { - _committedOnBucket1!.Skip(checkPoint).All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection - } - - [Fact] - public void should_not_load_the_commits_from_bucket2() - { - _committedOnBucket2!.All(x => !_loaded!.Contains(x)).Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_from_a_particular_checkpoint_to_a_checkpoint : PersistenceEngineConcern - { - private readonly List _committed = []; - private List? _loaded; - private const int startCheckpoint = 2; - private int endCheckpoint; - - protected override void Context() - { - var committedOnBucket1 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, Bucket.Default).Select(c => c.CommitId).ToList(); - var committedOnBucket2 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "Bucket1").Select(c => c.CommitId).ToList(); - _committed.AddRange(committedOnBucket1); - _committed.AddRange(committedOnBucket2); - endCheckpoint = (2 * (ConfiguredPageSizeForTesting + 1)) - 1; - } - - protected override void Because() - { - _loaded = Persistence.GetFromTo(startCheckpoint, endCheckpoint).Select(c => c.CommitId).ToList(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint_to_the_checkpoint() - { - _loaded!.Count.Should().Be(endCheckpoint - startCheckpoint); - } - - [Fact] - public void should_load_only_the_commits_starting_from_the_checkpoint_to_the_checkpoint() - { - _committed - .Skip(startCheckpoint) - .Take(_committed.Count - startCheckpoint - 1) - //.Take(endCheckpoint - startCheckpoint) - .All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection - } - } - -#if MSTEST - [TestClass] -#endif - public class when_paging_over_all_commits_of_a_bucket_from_a_particular_checkpoint_to_a_checkpoint : PersistenceEngineConcern - { - private List? _committedOnBucket1; - private List? _committedOnBucket2; - private List? _loaded; - private const int startCheckpoint = 2; - private int endCheckpoint; - - protected override void Context() - { - _committedOnBucket1 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "b1").Select(c => c.CommitId).ToList(); - _committedOnBucket2 = Persistence.CommitMany(ConfiguredPageSizeForTesting + 1, null, "b2").Select(c => c.CommitId).ToList(); - _committedOnBucket1.AddRange(Persistence.CommitMany(4, null, "b1").Select(c => c.CommitId)); - endCheckpoint = ((2 * (ConfiguredPageSizeForTesting + 1)) + 4) - 1; - } - - protected override void Because() - { - _loaded = Persistence.GetFromTo("b1", startCheckpoint, endCheckpoint).Select(c => c.CommitId).ToList(); - } - - [Fact] - public void should_load_the_same_number_of_commits_which_have_been_persisted_starting_from_the_checkpoint_to_the_checkpoint() - { - _loaded!.Count.Should().Be(_committedOnBucket1!.Count - startCheckpoint - 1); - } - - [Fact] - public void should_load_only_the_commits_on_bucket1_starting_from_the_checkpoint_to_the_checkpoint() - { - _committedOnBucket1! - .Skip(startCheckpoint) - .Take(_committedOnBucket1!.Count - startCheckpoint - 1) - .All(x => _loaded!.Contains(x)).Should().BeTrue(); // all commits should be found in loaded collection - } - - [Fact] - public void should_not_load_the_commits_from_bucket2() - { - _committedOnBucket2!.All(x => !_loaded!.Contains(x)).Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_commits_from_the_year_1_AD : PersistenceEngineConcern - { - private Exception? _thrown; - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - _thrown = Catch.Exception(() => Persistence.GetFrom(Bucket.Default, 0).FirstOrDefault()); - } - - [Fact] - public void should_NOT_throw_an_exception() - { - _thrown.Should().BeNull(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_all_commits : PersistenceEngineConcern - { - protected override void Context() - { - Persistence.CommitSingle(); - } - - protected override void Because() - { - Persistence.Purge(); - } - - [Fact] - public void should_not_find_any_commits_stored() - { - Persistence.GetFrom(Bucket.Default, 0).Count().Should().Be(0); - } - - [Fact] - public void should_not_find_any_streams_to_snapshot() - { - Persistence.GetStreamsToSnapshot(0).Count().Should().Be(0); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_invoking_after_disposal : PersistenceEngineConcern - { - private Exception? _thrown; - - protected override void Context() - { - Persistence.Dispose(); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Persistence.CommitSingle()); - } - - [Fact] - public void should_throw_an_ObjectDisposedException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_a_stream_with_the_same_id_as_a_stream_same_bucket : PersistenceEngineConcern - { - private string? _streamId; - private static Exception? _thrown; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - Persistence.Commit(_streamId.BuildAttempt()); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Persistence.Commit(_streamId!.BuildAttempt())); - } - - [Fact] - public void should_throw() - { - _thrown.Should().NotBeNull(); - } - - [Fact] - public void should_be_duplicate_commit_exception() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_a_stream_with_the_same_id_as_a_stream_in_another_bucket : PersistenceEngineConcern - { - private const string _bucketAId = "a"; - private const string _bucketBId = "b"; - private string? _streamId; - private static CommitAttempt? _attemptForBucketB; - private static Exception? _thrown; - private DateTime _attemptACommitStamp; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - DateTime now = SystemTime.UtcNow; - Persistence.Commit(_streamId.BuildAttempt(now, _bucketAId)); - _attemptACommitStamp = Persistence.GetFrom(_bucketAId, _streamId, 0, int.MaxValue).First().CommitStamp; - _attemptForBucketB = _streamId.BuildAttempt(now.Subtract(TimeSpan.FromDays(1)), _bucketBId); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Persistence.Commit(_attemptForBucketB!)); - } - - [Fact] - public void should_succeed() - { - _thrown.Should().BeNull(); - } - - [Fact] - public void should_persist_to_the_correct_bucket() - { - ICommit[] stream = Persistence.GetFrom(_bucketBId, _streamId!, 0, int.MaxValue).ToArray(); - stream.Should().NotBeNull(); - stream.Length.Should().Be(1); - } - - [Fact] - public void should_not_affect_the_stream_from_the_other_bucket() - { - ICommit[] stream = Persistence.GetFrom(_bucketAId, _streamId!, 0, int.MaxValue).ToArray(); - stream.Should().NotBeNull(); - stream.Length.Should().Be(1); - stream[0].CommitStamp.Should().Be(_attemptACommitStamp); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_saving_a_snapshot_for_a_stream_with_the_same_id_as_a_stream_in_another_bucket : PersistenceEngineConcern - { - private const string _bucketAId = "a"; - private const string _bucketBId = "b"; - - private string? _streamId; - - private static Snapshot? _snapshot; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - _snapshot = new Snapshot(_bucketBId, _streamId, 1, "Snapshot"); - Persistence.Commit(_streamId.BuildAttempt(bucketId: _bucketAId)); - Persistence.Commit(_streamId.BuildAttempt(bucketId: _bucketBId)); - } - - protected override void Because() - { - Persistence.AddSnapshot(_snapshot!); - } - - [Fact] - public void should_affect_snapshots_from_another_bucket() - { - Persistence.GetSnapshot(_bucketAId, _streamId!, _snapshot!.StreamRevision).Should().BeNull(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_commits_from_a_particular_point_in_time_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcern - { - private const string _bucketAId = "a"; - private const string _bucketBId = "b"; - - private static DateTime _now; - private static ICommit[]? _returnedCommits; - private CommitAttempt? _commitToBucketB; - - protected override void Context() - { - _now = SystemTime.UtcNow.AddYears(1); - - var commitToBucketA = Guid.NewGuid().ToString().BuildAttempt(_now.AddSeconds(1), _bucketAId); - - Persistence.Commit(commitToBucketA); - commitToBucketA = commitToBucketA.BuildNextAttempt(); - Persistence.Commit(commitToBucketA); - commitToBucketA = commitToBucketA.BuildNextAttempt(); - Persistence.Commit(commitToBucketA); - Persistence.Commit(commitToBucketA.BuildNextAttempt()); - - _commitToBucketB = Guid.NewGuid().ToString().BuildAttempt(_now.AddSeconds(1), _bucketBId); - - Persistence.Commit(_commitToBucketB); - } - - protected override void Because() - { - _returnedCommits = Persistence.GetFrom(_bucketAId, _now).ToArray(); - } - - [Fact] - public void should_not_return_commits_from_other_buckets() - { - _returnedCommits!.Any(c => c.CommitId.Equals(_commitToBucketB!.CommitId)).Should().BeFalse(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_getting_all_commits_since_checkpoint_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcern - { - private ICommit[]? _commits; - - protected override void Context() - { - const string bucketAId = "a"; - const string bucketBId = "b"; - Persistence.Commit(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketAId)); - Persistence.Commit(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketBId)); - Persistence.Commit(Guid.NewGuid().ToString().BuildAttempt(bucketId: bucketAId)); - } - - protected override void Because() - { - _commits = Persistence.GetFrom(0).ToArray(); - } - - [Fact] - public void should_not_be_empty() - { - _commits.Should().NotBeEmpty(); - } - - [Fact] - public void should_be_in_order_by_checkpoint() - { - Int64 checkpoint = 0; - foreach (var commit in _commits!) - { - Int64 commitCheckpoint = commit.CheckpointToken; - commitCheckpoint.Should().BeGreaterThan(checkpoint); - checkpoint = commit.CheckpointToken; - } - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_all_commits_and_there_are_streams_in_multiple_buckets : PersistenceEngineConcern - { - private const string _bucketAId = "a"; - private const string _bucketBId = "b"; - - private string? _streamId; - - protected override void Context() - { - _streamId = Guid.NewGuid().ToString(); - Persistence.Commit(_streamId.BuildAttempt(bucketId: _bucketAId)); - Persistence.Commit(_streamId.BuildAttempt(bucketId: _bucketBId)); - var _snapshotA = new Snapshot(bucketId: _bucketAId, _streamId, 1, "SnapshotA"); - Persistence.AddSnapshot(_snapshotA); - var _snapshotB = new Snapshot(bucketId: _bucketBId, _streamId, 1, "SnapshotB"); - Persistence.AddSnapshot(_snapshotB); - } - - protected override void Because() - { - Persistence.Purge(); - } - - [Fact] - public void should_purge_all_commits_stored_in_bucket_a() - { - Persistence.GetFrom(_bucketAId, 0).Count().Should().Be(0); - } - - [Fact] - public void should_purge_all_commits_stored_in_bucket_b() - { - Persistence.GetFrom(_bucketBId, 0).Count().Should().Be(0); - } - - [Fact] - public void should_purge_all_streams_to_snapshot_in_bucket_a() - { - Persistence.GetStreamsToSnapshot(_bucketAId, 0).Count().Should().Be(0); - } - - [Fact] - public void should_purge_all_streams_to_snapshot_in_bucket_b() - { - Persistence.GetStreamsToSnapshot(_bucketBId, 0).Count().Should().Be(0); - } - } - - [Serializable] - public class TestEvent - { - public String? S { get; set; } - } - -#if MSTEST - [TestClass] -#endif - public class when_calling_CommitChanges : PersistenceEngineConcern - { - private Guid? _commitId; - private ICommit? _persistedCommit; - private ICommit[]? _commits; - - protected override void Because() - { - var eventStore = new OptimisticEventStore(Persistence, null, null); - using IEventStream stream = eventStore.OpenStream(Guid.NewGuid()); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " } }); - _commitId = Guid.NewGuid(); - _persistedCommit = stream.CommitChanges(_commitId.Value); - - _commits = Persistence.GetFrom(0).ToArray(); - } - - [Fact] - public void A_Commit_had_been_persisted() - { - _persistedCommit.Should().NotBeNull(); - _persistedCommit!.CommitId.Should().Be(_commitId!.Value); - _persistedCommit.CommitSequence.Should().Be(1); - _persistedCommit.Events.Count.Should().Be(1); - _persistedCommit.Events.Single().Body.Should().BeOfType(); - ((TestEvent)_persistedCommit.Events.Single().Body!).S.Should().Be("Hi "); - } - - [Fact] - public void Should_have_expected_number_of_commits() - { - _commits!.Length.Should().Be(1); - // if should have the right event - _commits[0].CommitId.Should().Be(_commitId!.Value); - _commits[0].Events.Count.Should().Be(1); - _commits[0].Events.Single().Body.Should().BeOfType(); - ((TestEvent)_commits[0].Events.Single().Body!).S.Should().Be("Hi "); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_gettingFromCheckpoint_amount_of_commits_exceeds_PageSize : PersistenceEngineConcern - { - private ICommit[]? _commits; - private int _moreThanPageSize; - - protected override void Because() - { - _moreThanPageSize = ConfiguredPageSizeForTesting + 1; - var eventStore = new OptimisticEventStore(Persistence, null, null); - for (int i = 0; i < _moreThanPageSize; i++) - { - using IEventStream stream = eventStore.OpenStream(Guid.NewGuid()); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " + i } }); - stream.CommitChanges(Guid.NewGuid()); - } - _commits = Persistence.GetFrom(0).ToArray(); - } - - [Fact] - public void Should_have_expected_number_of_commits() - { - _commits!.Length.Should().Be(_moreThanPageSize); - } - } - - /// - /// - /// NEventStore.Persistence.Sql issue #54 - /// GetStreamsToSnapshot fails when number of streams exceeds PageSize. - /// - /// - /// Query implementation was wrong: checking how the query and the pagination were implemented - /// it was lacking to check for C.StreamId > @StreamId. - /// which is needed to page through the results (similar to @Coalesce in GetCommitsFromStartingRevision) - /// - /// -#if MSTEST - [TestClass] -#endif - public class when_getting_streams_to_snapshot_amount_exceeds_PageSize : PersistenceEngineConcern - { - private int _moreThanPageSize; - private Guid _lastStreamId; - - protected override void Because() - { - _moreThanPageSize = ConfiguredPageSizeForTesting + 1; - var eventStore = new OptimisticEventStore(Persistence, null, null); - for (int i = 0; i < _moreThanPageSize; i++) - { - _lastStreamId = Guid.NewGuid(); - using IEventStream stream = eventStore.OpenStream(_lastStreamId); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " + i } }); - stream.CommitChanges(Guid.NewGuid()); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " + i } }); - stream.CommitChanges(Guid.NewGuid()); - stream.Add(new EventMessage { Body = new TestEvent() { S = "Hi " + i } }); - stream.CommitChanges(Guid.NewGuid()); - } - } - - [Fact] - public void GetStreamsToSnapshot_does_not_crash_and_returns_all_the_streams() - { - Assert.DoesNotThrow(() => - { - var streamHeads = Persistence.GetStreamsToSnapshot(0).ToArray(); - streamHeads.Length.Should().Be(_moreThanPageSize); - }); - } - - [Fact] - public void GetFrom_does_not_crash_and_returns_all_the_streams() - { - Assert.DoesNotThrow(() => - { - var streamHeads = Persistence.GetFrom(Bucket.Default, _lastStreamId.ToString(), 0, int.MaxValue).ToArray(); - streamHeads.Length.Should().Be(3); - }); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_a_payload_is_large : PersistenceEngineConcern - { - [Fact] - public void can_commit() - { - const int bodyLength = 100000; - var attempt = new CommitAttempt( - Bucket.Default, - Guid.NewGuid().ToString(), - 1, - Guid.NewGuid(), - 1, - DateTime.UtcNow, - new Dictionary(), - [new EventMessage { Body = new string('a', bodyLength) }]); - Persistence.Commit(attempt); - - ICommit commits = Persistence.GetFrom(0).Single(); - commits.Events.Single().Body.ToString()!.Length.Should().Be(bodyLength); - } - } - - /// - /// We are adapting the tests to use 3 different frameworks: - /// - XUnit: the attached test runner does the job (fixture setup and cleanup) - /// - NUnit (.net core project) - /// - MSTest (.net core project) - /// - public abstract class PersistenceEngineConcern : SpecificationBase -#if XUNIT - , IUseFixture -#endif -#if NUNIT || MSTEST - , IDisposable -#endif - { - protected PersistenceEngineFixture? Fixture { get; private set; } - - protected IPersistStreams Persistence - { - get { return Fixture!.Persistence!; } - } - - protected int ConfiguredPageSizeForTesting - { - get { return 2; } - } - - /// - /// Can be used by XUNIT to initialize the tests. - /// - public void SetFixture(PersistenceEngineFixture data) - { - Fixture = data; - Fixture.Initialize(ConfiguredPageSizeForTesting); - } - -#if NUNIT || MSTEST - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - Fixture?.Dispose(); - } - - /// - /// - /// This code was meant to be run right before every test in the fixture to give time - /// to do further initialization before the PersistenceEngineFixture was created. - /// Unfortunately the 3 frameworks - /// have very different ways of doing this: - /// - NUnit: TestFixtureSetUp - /// - MSTest: ClassInitialize (not inherited, will be ignored if defined on a base class) - /// - xUnit: IUseFixture + SetFixture - /// We need a way to also have some configuration before the PersistenceEngineFixture is created. - /// - /// - /// We've decided to use the test constructor to do the job, it's your responsibility to guarantee - /// One time initialization (for anything that need it, if you have multiple tests on a fixture) - /// depending on the framework you are using. - /// - /// - /// quick workaround: - /// - change the parameters of the "Fixture" properties. - /// - call the 'Reinitialize()' method can be called to rerun the initialization after we changed the configuration - /// in the constructor. - /// or - /// - call the SetFixture() to reinitialize everything. - /// - /// - protected PersistenceEngineConcern() : this(new PersistenceEngineFixture()) - { } - - protected PersistenceEngineConcern(PersistenceEngineFixture fixture) - { - SetFixture(fixture); - } -#endif - } - - public partial class PersistenceEngineFixture : IDisposable - { - public IPersistStreams? Persistence { get; private set; } - - private readonly Func _createPersistence; - -#if NET462 - private bool _tracking = false; - private string _trackingInstanceName; - - /// - /// Automatic Performance Counters and tracking was disabled for full - /// framework tests because their initialization - /// can fail when the tests run on build machines (like AppVeyor and similar). - /// You can enable it back calling this function before - /// - /// - /// - public PersistenceEngineFixture TrackPerformanceInstance(string instanceName = "tests") - { - _trackingInstanceName = instanceName; - _tracking = true; - return this; - } -#endif - - public void Initialize(int pageSize) - { -#if NET462 - // performance counters cab be disabled for full framework tests because their initialization - // can fail when the tests run on build machines (like AppVeyor and similar) - if (_tracking) - { - Persistence = new NEventStore.Diagnostics.PerformanceCounterPersistenceEngine(_createPersistence(pageSize), _trackingInstanceName); - } - else - { - Persistence = _createPersistence(pageSize); - } -#else - Persistence = _createPersistence(pageSize); -#endif - Persistence.Initialize(); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (Persistence?.IsDisposed == false) - { - Persistence.Drop(); - Persistence.Dispose(); - } - } - } -} - -#pragma warning restore 169 // ReSharper disable InconsistentNaming -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs b/src/NEventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs deleted file mode 100644 index 3fa652324..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. - -[assembly: AssemblyTitle("NEventStore.Persistence.AcceptanceTests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore.Persistence.AcceptanceTests")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM - -[assembly: Guid("f85a343a-1aae-4a01-a91d-efd76322d9d8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Persistence.AcceptanceTests/SerializationTests.cs b/src/NEventStore.Persistence.AcceptanceTests/SerializationTests.cs deleted file mode 100644 index 50d7a3e8f..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/SerializationTests.cs +++ /dev/null @@ -1,270 +0,0 @@ -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable 169 // ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles -#pragma warning disable S101 // Types should be named in PascalCase - -namespace NEventStore.Serialization.AcceptanceTests -{ -#if MSTEST - [TestClass] -#endif - public class when_serializing_a_simple_message : SerializationConcern - { - private readonly SimpleMessage _message = new SimpleMessage().Populate(); - private SimpleMessage? _deserialized; - private byte[]? _serialized; - - protected override void Context() - { - _serialized = Serializer.Serialize(_message); - } - - protected override void Because() - { - _deserialized = Serializer.Deserialize(_serialized!); - } - - [Fact] - public void should_deserialize_a_message_which_contains_the_same_Id_as_the_serialized_message() - { - _deserialized!.Id.Should().Be(_message.Id); - } - - [Fact] - public void should_deserialize_a_message_which_contains_the_same_Value_as_the_serialized_message() - { - _deserialized!.Value.Should().Be(_message.Value); - } - - [Fact] - public void should_deserialize_a_message_which_contains_the_same_Created_value_as_the_serialized_message() - { - _deserialized!.Created.Should().Be(_message.Created); - } - - [Fact] - public void should_deserialize_a_message_which_contains_the_same_Count_as_the_serialized_message() - { - _deserialized!.Count.Should().Be(_message.Count); - } - - [Fact] - public void should_deserialize_a_message_which_contains_the_number_of_elements_as_the_serialized_message() - { - _deserialized!.Contents.Count.Should().Be(_message.Contents.Count); - } - - [Fact] - public void should_deserialize_a_message_which_contains_the_same_Contents_as_the_serialized_message() - { - _deserialized!.Contents.SequenceEqual(_message.Contents).Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_serializing_a_list_of_event_messages : SerializationConcern - { - private readonly List Messages = new List - { - new EventMessage {Body = "some value"}, - new EventMessage {Body = 42}, - new EventMessage {Body = new SimpleMessage()} - }; - - private List? _deserialized; - private byte[]? _serialized; - - protected override void Context() - { - _serialized = Serializer.Serialize(Messages); - } - - protected override void Because() - { - _deserialized = Serializer.Deserialize>(_serialized!); - } - - [Fact] - public void should_deserialize_the_same_number_of_event_messages_as_it_serialized() - { - Messages.Count.Should().Be(_deserialized!.Count); - } - - [Fact] - public void should_deserialize_the_complex_types_within_the_event_messages() - { - _deserialized!.Last().Body.Should().BeOfType(); - } - - [Fact] - public void should_deserialize_event_messages_with_a_non_null_headers_dictionary() - { - _deserialized!.Should().OnlyContain(message => message.Headers != null); - } - - [Fact] - public void should_deserialize_event_messages_with_empty_headers_when_none_were_serialized() - { - _deserialized!.Should().OnlyContain(message => message.Headers.Count == 0); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_serializing_an_event_message_with_headers : SerializationConcern - { - private readonly EventMessage _message = new EventMessage { Body = "some value" }; - private EventMessage? _deserialized; - private byte[]? _serialized; - - protected override void Context() - { - _message.Headers["HeaderKey"] = "SomeValue"; - _message.Headers["AnotherHeader"] = "AnotherValue"; - _serialized = Serializer.Serialize(_message); - } - - protected override void Because() - { - _deserialized = Serializer.Deserialize(_serialized!); - } - - [Fact] - public void should_deserialize_the_headers_dictionary() - { - _deserialized!.Headers.Should().NotBeNull(); - } - - [Fact] - public void should_deserialize_the_same_number_of_headers() - { - _deserialized!.Headers.Count.Should().Be(_message.Headers.Count); - } - - [Fact] - public void should_deserialize_string_header_values() - { - _deserialized!.Headers["HeaderKey"].Should().Be("SomeValue"); - _deserialized.Headers["AnotherHeader"].Should().Be("AnotherValue"); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_serializing_a_list_of_commit_headers : SerializationConcern - { - private readonly Dictionary _headers = new Dictionary - { - {"HeaderKey", "SomeValue"}, - {"AnotherKey", 42}, - {"AndAnotherKey", Guid.NewGuid()}, - {"LastKey", new SimpleMessage()} - }; - - private Dictionary? _deserialized; - private byte[]? _serialized; - - protected override void Context() - { - _serialized = Serializer.Serialize(_headers); - } - - protected override void Because() - { - _deserialized = Serializer.Deserialize>(_serialized!); - } - - [Fact] - public void should_deserialize_the_same_number_of_event_messages_as_it_serialized() - { - _headers.Count.Should().Be(_deserialized!.Count); - } - - [Fact] - public void should_deserialize_the_the_complex_types_within_the_event_messages() - { - _deserialized!.Last().Value.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_serializing_an_untyped_payload_on_a_snapshot : SerializationConcern - { - private Snapshot? _deserialized; - private IDictionary>? _payload; - private byte[]? _serialized; - private Snapshot? _snapshot; - - protected override void Context() - { - _payload = new Dictionary>(); - _snapshot = new Snapshot(Guid.NewGuid().ToString(), 42, _payload); - _serialized = Serializer.Serialize(_snapshot); - } - - protected override void Because() - { - _deserialized = Serializer.Deserialize(_serialized!); - } - - [Fact] - public void should_correctly_deserialize_the_untyped_payload_contents() - { - _deserialized!.Payload.Should().BeEquivalentTo(_snapshot!.Payload); - } - - [Fact] - public void should_correctly_deserialize_the_untyped_payload_type() - { - _deserialized!.Payload.Should().BeOfType(_snapshot!.Payload.GetType()); - } - } - - public abstract class SerializationConcern : SpecificationBase - { - private readonly SerializerFixture _data; - - public ISerialize Serializer - { - get { return _data.Serializer; } - } - - protected SerializationConcern() - { - _data = new SerializerFixture(); - } - } - - public partial class SerializerFixture - { - private readonly Func _createSerializer; - private ISerialize? _serializer; - - public ISerialize Serializer - { - get { return _serializer ??= _createSerializer(); } - } - } -} - -#pragma warning restore S101 // Types should be named in PascalCase -#pragma warning restore 169 // ReSharper disable InconsistentNaming -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Persistence.AcceptanceTests/SimpleMessage.cs b/src/NEventStore.Persistence.AcceptanceTests/SimpleMessage.cs deleted file mode 100644 index 84f519312..000000000 --- a/src/NEventStore.Persistence.AcceptanceTests/SimpleMessage.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NEventStore.Persistence.AcceptanceTests -{ - [Serializable] - public class SimpleMessage - { - public SimpleMessage() - { - Contents = []; - } - - public Guid Id { get; set; } - public DateTime Created { get; set; } - public string? Value { get; set; } - public int Count { get; set; } - - public List Contents { get; private set; } - } -} \ No newline at end of file diff --git a/src/NEventStore.PollingClient/AsyncPollingClient.cs b/src/NEventStore.PollingClient/AsyncPollingClient.cs deleted file mode 100644 index e6a3682db..000000000 --- a/src/NEventStore.PollingClient/AsyncPollingClient.cs +++ /dev/null @@ -1,476 +0,0 @@ -using NEventStore.Logging; -using NEventStore.Persistence; -using Microsoft.Extensions.Logging; - -namespace NEventStore.PollingClient -{ - /// - /// A Polling Client that uses the Asynchronous API of the store. - /// - public class AsyncPollingClient : IDisposable, IAsyncDisposable - { - /// - /// Decorator used to enable Commit Re-Sequencing. - /// During normal operations Commits might arrive out of order, or there can be holes in the sequence for whatever reason. - /// In a rebuild scenario the stream is stable and commits will be read in the correct order, holes can be safely skipped. - /// - private sealed class CommitSequencer : IAsyncObserver - { - private readonly AsyncPollingClientObserver _observer; - private readonly ILogger _logger = LogFactory.BuildLogger(typeof(CommitSequencer)); - /// - /// How many retries we did on a hole. - /// - public int NumOfRetries { get; private set; } - /// - /// Max number of retries before skipping the hole. - /// - public int MaxNumOfRetriesBeforeSkip { get; set; } = 5; - - public CommitSequencer(AsyncPollingClientObserver observer, long checkpoint) - { - _observer = observer; - _observer.ProcessedCheckpoint = checkpoint; - } - - public Task OnCompletedAsync(CancellationToken cancellationToken = default) - { - return _observer.OnCompletedAsync(cancellationToken); - } - - public Task OnErrorAsync(Exception ex, CancellationToken cancellationToken = default) - { - return _observer.OnErrorAsync(ex, cancellationToken); - } - - public Task OnNextAsync(ICommit value, CancellationToken cancellationToken = default) - { - if (value.CheckpointToken != _observer.ProcessedCheckpoint + 1) - { - if (NumOfRetries < MaxNumOfRetriesBeforeSkip) - { - NumOfRetries++; - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Hole detected on {Checkpoint} - {NumOfRetries}", value.CheckpointToken, NumOfRetries); - } - // stop the reading, the caller will wait then resume reading from the same checkpoint - return Task.FromResult(false); - } - // we reached the max number of retries, skip the hole - if (_logger.IsEnabled(LogLevel.Warning)) - { - _logger.LogWarning("Skipping hole on {Checkpoint}", value.CheckpointToken); - } - } - - NumOfRetries = 0; - return _observer.OnNextAsync(value, cancellationToken); - } - } - - /// - /// Async Observer Decorator that wraps the original observer: it is used to: - /// - Track the last processed commit. - /// - Track if the user request to stop the polling from inside the observer. - /// - private sealed class AsyncPollingClientObserver : IAsyncObserver - { -#if NET8_0_OR_GREATER - private static readonly Task ContinuePollingTask = Task.FromResult(true); - private static readonly Task StopPollingTask = Task.FromResult(false); -#endif - private readonly IAsyncObserver _observer; - - public bool StopPolling { get; private set; } - /// - /// The last correctly processed commit. - /// - public long ProcessedCheckpoint { get; internal set; } - - public AsyncPollingClientObserver(IAsyncObserver observer) - { - _observer = observer; - } - public Task OnCompletedAsync(CancellationToken cancellationToken = default) - { - return _observer.OnCompletedAsync(cancellationToken); - } - - public Task OnErrorAsync(Exception ex, CancellationToken cancellationToken = default) - { - return _observer.OnErrorAsync(ex, cancellationToken); - } - -#if NET8_0_OR_GREATER - public Task OnNextAsync(ICommit value, CancellationToken cancellationToken = default) - { - StopPolling = false; - var goOnTask = _observer.OnNextAsync(value, cancellationToken); - - // The common catch-up path often uses observers that complete synchronously after - // recording the commit. Modern targets can detect that completed task and update - // checkpoint/stop state without allocating an async state machine per commit. - // Faulted or incomplete tasks intentionally flow through the awaited path so - // exception and cancellation behavior stays identical to the compatibility target. - if (goOnTask.IsCompletedSuccessfully) - { - return ToCompletedTask(CompleteOnNext(value, goOnTask.Result)); - } - - return CompleteOnNextAsync(value, goOnTask); - } -#else - public async Task OnNextAsync(ICommit value, CancellationToken cancellationToken = default) - { - StopPolling = false; - var goOn = await _observer.OnNextAsync(value, cancellationToken).ConfigureAwait(false); - return CompleteOnNext(value, goOn); - } -#endif - - private bool CompleteOnNext(ICommit value, bool goOn) - { - StopPolling = !goOn; - ProcessedCheckpoint = value.CheckpointToken; - return goOn; - } - -#if NET8_0_OR_GREATER - private async Task CompleteOnNextAsync(ICommit value, Task goOnTask) - { - return CompleteOnNext(value, await goOnTask.ConfigureAwait(false)); - } - - private static Task ToCompletedTask(bool goOn) - { - return goOn ? ContinuePollingTask : StopPollingTask; - } -#endif - } - - private readonly ILogger _logger; - private readonly IPersistStreams _persistStreams; - private readonly AsyncPollingClientObserver _commitObserver; - private readonly Int32 _waitInterval; - private readonly int _holeDetectionWaitInterval; - private Func? _pollingFunc; - private Int64 _checkpointToken; - private CancellationTokenSource? _cancellationTokenSource; - private Task? _pollingTask; - private int _isPolling; - private CommitSequencer? _commitSequencer; - - /// - /// Creates an NEventStore Polling Client - /// - /// the store to check - /// the observer to notify - /// Interval in Milliseconds to wait when the provider - /// return no more commit and the next request - /// Interval in Milliseconds to wait before retry when a hole was detected in the stream. 0 = disable hole detection. - public AsyncPollingClient( - IPersistStreams persistStreams, - IAsyncObserver commitObserver, - Int32 waitInterval = 100, - Int32 holeDetectionWaitInterval = 0) - { - if (commitObserver is null) - { - throw new ArgumentNullException(nameof(commitObserver)); - } - - _persistStreams = persistStreams ?? throw new ArgumentNullException(nameof(persistStreams)); - _commitObserver = new AsyncPollingClientObserver(commitObserver); - _logger = LogFactory.BuildLogger(GetType()); - _waitInterval = waitInterval; - _holeDetectionWaitInterval = holeDetectionWaitInterval; - LastActivityTimestamp = DateTime.UtcNow; - } - - /// - /// Tells the caller the last tick count when the last activity occurred. This is useful for the caller - /// to setup Health check that verify if the polling client is really active and it is really loading new commits. - /// This value is obtained with DateTime.UtcNow - /// - public DateTime LastActivityTimestamp { get; private set; } - - /// - /// If the polling client encounter an exception it immediately retry, but we need to tell to the caller code - /// that the last polling encounter an error. This is needed to detect a polling client stuck as an example - /// with deserialization problems. - /// - public String? LastPollingError { get; private set; } - -#if NET9_0_OR_GREATER - private readonly Lock _startLock = new Lock(); -#else - private readonly object _startLock = new object(); -#endif - - /// - /// Start the polling client. - /// - public void Start(Int64 checkpointToken = 0) - { - lock (_startLock) - { - if (_isDisposed) - { - throw new ObjectDisposedException(GetType().FullName); - } - - if (_pollingTask is not null) - { - throw new InvalidOperationException("The polling client is already started."); - } - - _cancellationTokenSource = new CancellationTokenSource(); - ConfigurePollingClient(checkpointToken); - StartPollingThread(_cancellationTokenSource.Token); - } - } - - /// - /// Start the polling client. - /// - public void Start(string bucketId, Int64 checkpointToken = 0) - { - lock (_startLock) - { - if (_isDisposed) - { - throw new ObjectDisposedException(GetType().FullName); - } - - if (_pollingTask is not null) - { - throw new InvalidOperationException("The polling client is already started."); - } - - _cancellationTokenSource = new CancellationTokenSource(); - ConfigurePollingClient(checkpointToken, bucketId); - StartPollingThread(_cancellationTokenSource.Token); - } - } - - /// - /// Configure the polling function to get commits from the store. - /// - internal void ConfigurePollingClient(Int64 checkpointToken = 0, string? bucketId = null) - { - _checkpointToken = checkpointToken; - IAsyncObserver commitObserver; - if (_holeDetectionWaitInterval > 0) - { - _commitSequencer = new CommitSequencer(_commitObserver, _checkpointToken); - commitObserver = _commitSequencer; - } - else - { - commitObserver = _commitObserver; - } - if (bucketId == null) - _pollingFunc = (long checkpointToken, CancellationToken cancellationToken) => _persistStreams.GetFromAsync(checkpointToken, commitObserver, cancellationToken); - else - _pollingFunc = (long checkpointToken, CancellationToken cancellationToken) => _persistStreams.GetFromAsync(bucketId, checkpointToken, commitObserver, cancellationToken); - } - - /// - /// Simply start the timer that will queue wake up tokens. - /// - private void StartPollingThread(CancellationToken cancellationToken) - { - if (_pollingFunc == null) return; - // Keep a direct handle to the worker task so StopAsync/Dispose can await the actual - // polling loop instead of repeatedly sleeping and hoping the worker has observed the - // cancellation request. That makes shutdown bounded by the in-flight poll itself, - // which is the real lifetime contract callers care about. - _pollingTask = Task.Run(() => PollingLoopAsync(cancellationToken), CancellationToken.None); - } - - private async Task PollingLoopAsync(CancellationToken cancellationToken) - { - try - { - while (!cancellationToken.IsCancellationRequested && !_commitObserver.StopPolling) - { - // Poll again immediately after consuming commits so catch-up paths can drain the - // store as fast as the observer allows. The idle delay is only useful when the - // previous poll did not advance the checkpoint and we genuinely need to back off. - bool madeProgress = await PollAsyncInternal(cancellationToken).ConfigureAwait(false); - if (!madeProgress && !cancellationToken.IsCancellationRequested && !_commitObserver.StopPolling) - { - await Task.Delay(_waitInterval, cancellationToken).ConfigureAwait(false); - } - } - } - catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) - { - // Cancellation is the normal stop path. Swallow it here so StopAsync can await the - // worker task directly without turning routine shutdown into an error. - } - catch (Exception ex) - { - if (_logger.IsEnabled(LogLevel.Error)) - { - _logger.LogError(ex, "Error during Polling operation"); - } - LastPollingError = ex.Message; - } - } - - /// - /// Stop the polling client. - /// - public async Task StopAsync() - { - var cancellationTokenSource = _cancellationTokenSource; - var pollingTask = _pollingTask; - -#if NET8_0_OR_GREATER - if (cancellationTokenSource != null) - { - await cancellationTokenSource.CancelAsync().ConfigureAwait(false); - } -#else - cancellationTokenSource?.Cancel(); -#endif - _cancellationTokenSource = null; - _pollingTask = null; - - if (pollingTask == null) - { - return; - } - - try - { - await pollingTask.ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // The polling loop swallows cooperative cancellation, but keep this catch to avoid - // leaking a cancellation fault if a future refactor changes where the exception is observed. - } - } - - /// - /// Poll the store for new commits. - /// - public Task PollAsync(CancellationToken token) - { - // The public manual-poll API does not expose whether the checkpoint advanced, but - // the polling loop needs that signal internally. Returning the Task as Task - // preserves the public contract while avoiding an extra async wrapper allocation. - return PollAsyncInternal(token); - } - - private async Task PollAsyncInternal(CancellationToken token) - { - if (Interlocked.CompareExchange(ref _isPolling, 1, 0) == 0) - { - try - { - LastActivityTimestamp = DateTime.UtcNow; - bool madeProgress = false; - if (_commitSequencer == null) - { - long checkpointBeforePoll = _checkpointToken; - await _pollingFunc!(_checkpointToken, token).ConfigureAwait(false); - _checkpointToken = _commitObserver.ProcessedCheckpoint; - madeProgress = _checkpointToken != checkpointBeforePoll; - } - else - { - do - { - long checkpointBeforePoll = _checkpointToken; - await _pollingFunc!(_checkpointToken, token).ConfigureAwait(false); - _checkpointToken = _commitObserver.ProcessedCheckpoint; - madeProgress |= _checkpointToken != checkpointBeforePoll; - if (_commitObserver.StopPolling) - { - break; - } - // todo: should also check if the user requested to stop the polling from the observer ? - if (_commitSequencer.NumOfRetries > 0) - { - await Task.Delay(_holeDetectionWaitInterval, token).ConfigureAwait(false); - } - } - while (!token.IsCancellationRequested && _commitSequencer.NumOfRetries > 0); - } - - return madeProgress; - } - finally - { - Interlocked.Exchange(ref _isPolling, 0); - } - } - - return false; - } - - private Boolean _isDisposed; - - /// - /// Dispose the polling client. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose the polling client. - /// - protected virtual void Dispose(Boolean isDisposing) - { - if (_isDisposed) - { - return; - } - if (isDisposing) - { - StopAsync().GetAwaiter().GetResult(); - } - _isDisposed = true; - } - - /// - /// Dispose the polling client asynchronously. - /// - public async ValueTask DisposeAsync() - { - await DisposeAsync(true).ConfigureAwait(false); - GC.SuppressFinalize(this); - } - - /// - /// Dispose the polling client asynchronously. - /// - protected virtual async Task DisposeAsync(Boolean isDisposing) - { - if (_isDisposed) - { - return; - } - if (isDisposing) - { - await StopAsync().ConfigureAwait(false); - } - _isDisposed = true; - } - - ///// - ///// Finalizer. - ///// - //~AsyncPollingClient() - //{ - // Dispose(false); - //} - } -} diff --git a/src/NEventStore.PollingClient/CommitSequencer.cs b/src/NEventStore.PollingClient/CommitSequencer.cs deleted file mode 100644 index ea8a8883a..000000000 --- a/src/NEventStore.PollingClient/CommitSequencer.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Helpers; -using NEventStore.Logging; -using System.Globalization; - -namespace NEventStore.PollingClient -{ - /// - /// A commit sequencer that can be used with - /// - public class CommitSequencer - { - private readonly ILogger _logger; - - private readonly Func _commitCallback; - - private Int64 _lastCommitRead; - - private readonly Int32 _outOfSequenceTimeoutInMilliseconds; - - /// - /// Initializes a new instance of the CommitSequencer class. - /// - public CommitSequencer(Func commitCallback, long lastCommitRead, int outOfSequenceTimeoutInMilliseconds) - { - _logger = LogFactory.BuildLogger(GetType()); - _commitCallback = commitCallback; - _lastCommitRead = lastCommitRead; - _outOfSequenceTimeoutInMilliseconds = outOfSequenceTimeoutInMilliseconds; - } - - private DateTime? outOfSequenceTimestamp; - - /// - /// Handles the specified commit. - /// - public PollingClient2.HandlingResult Handle(ICommit commit) - { - var lc = commit.CheckpointToken; - if (lc == _lastCommitRead + 1) - { - //is ok no need to re-sequence. - return InnerHandleResult(commit, lc); - } - else if (_lastCommitRead >= lc) - { - if (_logger.IsEnabled(LogLevel.Warning)) - { - _logger.LogWarning(String.Format(CultureInfo.InvariantCulture, "Wrong sequence in commit, last read: {0} actual read: {1}", _lastCommitRead, lc)); - } - return PollingClient2.HandlingResult.MoveToNext; - } - - if (outOfSequenceTimestamp == null) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Sequencer found out of sequence, last dispatched: {LastDispatchedCheckpoint} now dispatching: {NowDispatchingCheckpoint}", _lastCommitRead, lc); - } - outOfSequenceTimestamp = DateTimeService.Now; - } - else - { - var interval = DateTimeService.Now.Subtract(outOfSequenceTimestamp.Value); - if (interval.TotalMilliseconds > _outOfSequenceTimeoutInMilliseconds) - { - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Sequencer out of sequence timeout after {TotalMilliseconds} ms, last dispatched: {LastDispatchedCheckpoint} now dispatching: {NowDispatchingCheckpoint}", interval.TotalMilliseconds, _lastCommitRead, lc); - } - return InnerHandleResult(commit, lc); - } - if (_logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug("Sequencer still out of sequence from {TotalMilliseconds} ms, last dispatched: {LastDispatchedCheckpoint} now dispatching: {NowDispatchingCheckpoint}", interval.TotalMilliseconds, _lastCommitRead, lc); - } - } - - return PollingClient2.HandlingResult.Retry; - } - - private PollingClient2.HandlingResult InnerHandleResult(ICommit commit, Int64 lc) - { - var innerReturn = _commitCallback(commit); - outOfSequenceTimestamp = null; - if (innerReturn == PollingClient2.HandlingResult.MoveToNext) - { - _lastCommitRead = lc; - } - return innerReturn; - } - } -} diff --git a/src/NEventStore.PollingClient/NEventStore.PollingClient.csproj b/src/NEventStore.PollingClient/NEventStore.PollingClient.csproj deleted file mode 100644 index b9cc3c621..000000000 --- a/src/NEventStore.PollingClient/NEventStore.PollingClient.csproj +++ /dev/null @@ -1,64 +0,0 @@ - - - netstandard2.0;$(ModernTargetFrameworks) - false - NEventStore.PollingClient - NEventStore.PollingClient - false - TRACE;DEBUG - - - NEventStore.PollingClient - NEventStore.PollingClient - NEventStore Dev Team - http://neventstore.org - false - The purpose of the EventStore is to represent a series of events as a stream. NEventStore is a persistence agnostic event sourcing library for .NET. The primary use is most often associated with CQRS. This package is an implementation of a Polling Client that reads data from an EventStore. - events, event sourcing, cqrs, storage, persistence, database - True - true - true - snupkg - true - true - True - True - NEventStore Dev Team - icon.png - Readme.md - https://github.com/NEventStore/NEventStore - git - license.txt - True - True - latest-recommended - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - True - \ - - - True - \ - - - True - \ - - - - - - diff --git a/src/NEventStore.PollingClient/PollingClient2.cs b/src/NEventStore.PollingClient/PollingClient2.cs deleted file mode 100644 index 814e85ed2..000000000 --- a/src/NEventStore.PollingClient/PollingClient2.cs +++ /dev/null @@ -1,277 +0,0 @@ -using NEventStore.Logging; -using NEventStore.Persistence; -using System.Collections.Concurrent; -using Microsoft.Extensions.Logging; - -namespace NEventStore.PollingClient -{ - /// - /// A Polling Client that uses the Synchronous API of the store. - /// - public class PollingClient2 : IDisposable - { - /// - /// Result of the handling of a commit. - /// - public enum HandlingResult - { - /// - /// Move to the next commit. - /// - MoveToNext = 0, - /// - /// Retry the current commit. - /// - Retry = 1, - /// - /// Stop the polling client. - /// - Stop = 2, - } - - private readonly ILogger _logger; - - private readonly Func _commitCallback; - - private readonly IPersistStreams _persistStreams; - - private readonly Int32 _waitInterval; - private readonly Thread _pollingThread; - private readonly System.Timers.Timer _pollingWakeUpTimer; - - private Func>? _pollingFunc; - - private Int64 _checkpointToken; - - /// - /// Creates an NEventStore Polling Client - /// - /// the store to check - /// callback to execute at each commit - /// Interval in Milliseconds to wait when the provider - /// return no more commit and the next request - public PollingClient2( - IPersistStreams persistStreams, - Func callback, - Int32 waitInterval = 100) - { - _commitCallback = callback ?? throw new ArgumentNullException(nameof(callback), "Cannot use polling client without callback"); - _persistStreams = persistStreams ?? throw new ArgumentNullException(nameof(persistStreams), "PersistStreams cannot be null"); - - _logger = LogFactory.BuildLogger(GetType()); - _waitInterval = waitInterval; - _pollingWakeUpTimer = new System.Timers.Timer(); - _pollingWakeUpTimer.Elapsed += (sender, e) => WakeUpPollingClient(); - _pollingWakeUpTimer.Interval = _waitInterval; - - //Create polling thread - _pollingThread = new Thread(InnerPollingLoop); - _pollingThread.Start(); - - LastActivityTimestamp = DateTime.UtcNow; - } - - /// - /// Tells the caller the last tick count when the last activity occurred. This is useful for the caller - /// to setup Health check that verify if the polling client is really active and it is really loading new commits. - /// This value is obtained with DateTime.UtcNow - /// - public DateTime LastActivityTimestamp { get; private set; } - - /// - /// If the polling client encounter an exception it immediately retry, but we need to tell to the caller code - /// that the last polling encounter an error. This is needed to detect a polling client stuck as an example - /// with deserialization problems. - /// - public String? LastPollingError { get; private set; } - - /// - /// Start the polling client. - /// - public void StartFrom(Int64 checkpointToken = 0) - { - _checkpointToken = checkpointToken; - ConfigurePollingFunction(); - StartPollingThread(); - } - - /// - /// Start the polling client. - /// - public void StartFromBucket(string bucketId, Int64 checkpointToken = 0) - { - _checkpointToken = checkpointToken; - ConfigurePollingFunction(bucketId); - StartPollingThread(); - } - - /// - /// Simply start the timer that will queue wake up tokens. - /// - private void StartPollingThread() - { - _pollingWakeUpTimer.Start(); - } - - /// - /// Configure the polling function to get commits from the store. - /// - internal void ConfigurePollingFunction(string? bucketId = null) - { - if (bucketId == null) - _pollingFunc = () => _persistStreams.GetFrom(_checkpointToken); - else - _pollingFunc = () => _persistStreams.GetFrom(bucketId, _checkpointToken); - } - - /// - /// Stop the polling client. - /// - public void Stop() - { - _stopRequest = true; - _pollingWakeUpTimer?.Stop(); - - WakeUpPollingClient(); - } - - /// - /// Poll now. - /// - public void PollNow() - { - WakeUpPollingClient(); - } - - /// - /// Add an object to wake up the polling client. - /// - private void WakeUpPollingClient() - { - //Avoid adding more than one wake up object. - if (Interlocked.CompareExchange(ref _isPolling, 1, 0) == 0) - { - //If we have not a wake up object add one. - if (_pollCollection.Count == 0) - _pollCollection.Add(new object()); - - Interlocked.Exchange(ref _isPolling, 0); - } - } - - private int _isPolling; - - private Boolean _stopRequest; - - /// - /// This blocking collection is used to Wake up the polling thread - /// and to ensure that only the polling thread is polling from - /// event stream. - /// - private readonly BlockingCollection _pollCollection = []; - - private void InnerPollingLoop(object? obj) - { - foreach (var _ in _pollCollection.GetConsumingEnumerable()) - { - //check stop request - if (_stopRequest) - return; - - if (InnerPoll()) - return; - } - } - - /// - /// Added to avoid flooding of logging during polling. - /// - private DateTime _lastPollingErrorLogTimestamp = DateTime.MinValue; - - /// - /// This is the inner polling function that does the polling and - /// returns true if there were errors that should stop the polling client. - /// - /// Returns true if we need to stop the outer cycle. - private bool InnerPoll() - { - LastActivityTimestamp = DateTime.UtcNow; - if (_pollingFunc == null) return false; - - try - { - var commits = _pollingFunc(); - - //if we have an error in the provider, the error will be thrown during enumeration - foreach (var commit in commits) - { - //We need to reset the error, because we read correctly a commit - LastPollingError = null; - LastActivityTimestamp = DateTime.UtcNow; - if (_stopRequest) - { - return true; - } - var result = _commitCallback(commit); - if (result == HandlingResult.Retry) - { - if (_logger.IsEnabled(LogLevel.Trace)) - { - _logger.LogTrace("Commit callback ask retry for checkpointToken: {CommitCheckpointToken} - last dispatched: {LastDispatchedCheckpointToken}", commit.CheckpointToken, _checkpointToken); - } - break; - } - else if (result == HandlingResult.Stop) - { - Stop(); - return true; - } - _checkpointToken = commit.CheckpointToken; - } - //if we reach here, we had no error contacting the persistence store. - LastPollingError = null; - } - catch (Exception ex) - { - // These exceptions are expected to be transient - LastPollingError = ex.Message; - - // These exceptions are expected to be transient, we log at maximum a log entry each minute. - if (DateTime.UtcNow.Subtract(_lastPollingErrorLogTimestamp).TotalMinutes > 1) - { - _logger.LogError(ex, "Error during polling client."); - _lastPollingErrorLogTimestamp = DateTime.UtcNow; - } - - //A transient reading error is possible, but we need to wait a little bit before retrying. - Thread.Sleep(1000); - } - - return false; - } - - private Boolean _isDisposed; - - /// - /// Dispose the polling client. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose the polling client. - /// - public void Dispose(Boolean isDisposing) - { - if (_isDisposed) return; - if (isDisposing) - { - Stop(); - } - _isDisposed = true; - } - } -} diff --git a/src/NEventStore.PollingClient/PollingClientException.cs b/src/NEventStore.PollingClient/PollingClientException.cs deleted file mode 100644 index 4db4e3f20..000000000 --- a/src/NEventStore.PollingClient/PollingClientException.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace NEventStore.PollingClient -{ - /// - /// ApplicationException has been deprecated in .NET Core - /// - public class PollingClientException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public PollingClientException() { } - /// - /// Initializes a new instance of the class. - /// - public PollingClientException(string message) : base(message) { } - /// - /// Initializes a new instance of the class. - /// - public PollingClientException(string message, Exception inner) : base(message, inner) { } - } -} diff --git a/src/NEventStore.PollingClient/Properties/ProjectAssemblyInfo.cs b/src/NEventStore.PollingClient/Properties/ProjectAssemblyInfo.cs deleted file mode 100644 index 0269eb2dc..000000000 --- a/src/NEventStore.PollingClient/Properties/ProjectAssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("NEventStore")] -[assembly: AssemblyDescription("")] -[assembly: Guid("9cb4668f-d7b2-4ad9-8f9b-2af9903d2db2")] \ No newline at end of file diff --git a/src/NEventStore.PollingClient/README.MD b/src/NEventStore.PollingClient/README.MD deleted file mode 100644 index cdb8186b4..000000000 --- a/src/NEventStore.PollingClient/README.MD +++ /dev/null @@ -1,208 +0,0 @@ -# Polling Client - -This project contains a 'reference' implementation of a Polling Client for NEventStore. - -Its duty is to constantly ask the NEventStore if there are new commits made by the users, read them and dispatch the committed events to their handlers. - -You are encouraged to write your own PollingClient that suits your needs, you can use the code in this project as a starting point. - -## New Polling client without RX - -To remove dependency from RX library, the old PollingClient was removed from the project. - -The entire code for OldPollingClient is included in this README, If you still need it in your project you can simply include this class in your code as well as the dependencies from RX. - -With the new PollingClient2 you should simply pass a Function that will be called to handle the commit, you can use Rx if you like that approach approach, or even better you can use TPL Dataflow that is probably more suited. - -If you want to use the new polling client with RX you can look at PollingClientRx inside Test project. - -### Old Polling Client Code - - namespace NEventStore.Client - { - using System; - using System.Collections.Generic; - using System.Reactive; - using System.Reactive.Subjects; - using System.Threading; - using System.Threading.Tasks; - using NEventStore.Logging; - using NEventStore.Persistence; - - /// - /// Represents a client that poll the storage for latest commits. - /// - public sealed class PollingClient : ClientBase - { - private readonly int _interval; - - public PollingClient(IPersistStreams persistStreams, int interval = 5000) : base(persistStreams) - { - if (persistStreams == null) - { - throw new ArgumentNullException("persistStreams"); - } - if (interval <= 0) - { - throw new ArgumentException(Messages.MustBeGreaterThanZero.FormatWith("interval")); - } - _interval = interval; - } - - /// - /// Observe commits from the sepecified checkpoint token. If the token is null, - /// all commits from the beginning will be observed. - /// - /// The checkpoint token. - /// - /// An instance. - /// - public override IObserveCommits ObserveFrom(Int64 checkpointToken = 0) - { - return new PollingObserveCommits(PersistStreams, _interval, null, checkpointToken); - } - - public override IObserveCommits ObserveFromBucket(string bucketId, Int64 checkpointToken = 0) - { - return new PollingObserveCommits(PersistStreams, _interval, bucketId, checkpointToken); - } - - private class PollingObserveCommits : IObserveCommits - { - private ILog Logger = LogFactory.BuildLogger(typeof (PollingClient)); - private readonly IPersistStreams _persistStreams; - private Int64 _checkpointToken; - private readonly int _interval; - private readonly string _bucketId; - private readonly Subject _subject = new Subject(); - private readonly CancellationTokenSource _stopRequested = new CancellationTokenSource(); - private TaskCompletionSource _runningTaskCompletionSource; - private int _isPolling = 0; - - public PollingObserveCommits(IPersistStreams persistStreams, int interval, string bucketId, Int64 checkpointToken = 0) - { - _persistStreams = persistStreams; - _checkpointToken = checkpointToken; - _interval = interval; - _bucketId = bucketId; - } - - public IDisposable Subscribe(IObserver observer) - { - return _subject.Subscribe(observer); - } - - public void Dispose() - { - _stopRequested.Cancel(); - _subject.Dispose(); - if (_runningTaskCompletionSource != null) - { - _runningTaskCompletionSource.TrySetResult(new Unit()); - } - } - - public Task Start() - { - if (_runningTaskCompletionSource != null) - { - return _runningTaskCompletionSource.Task; - } - _runningTaskCompletionSource = new TaskCompletionSource(); - PollLoop(); - return _runningTaskCompletionSource.Task; - } - - public void PollNow() - { - DoPoll(); - } - - private void PollLoop() - { - if (_stopRequested.IsCancellationRequested) - { - Dispose(); - return; - } - TaskHelpers.Delay(_interval, _stopRequested.Token) - .WhenCompleted(_ => - { - DoPoll(); - PollLoop(); - },_ => Dispose()); - } - - private void DoPoll() - { - if (Interlocked.CompareExchange(ref _isPolling, 1, 0) == 0) - { - try - { - var commits = _bucketId == null ? - _persistStreams.GetFrom(_checkpointToken) : - _persistStreams.GetFrom(_bucketId, _checkpointToken); - - foreach (var commit in commits) - { - if (_stopRequested.IsCancellationRequested) - { - _subject.OnCompleted(); - return; - } - _subject.OnNext(commit); - _checkpointToken = commit.CheckpointToken; - } - } - catch (Exception ex) - { - // These exceptions are expected to be transient - Logger.Error(ex.ToString()); - } - Interlocked.Exchange(ref _isPolling, 0); - } - } - } - } - - public abstract class ClientBase - { - private readonly IPersistStreams _persistStreams; - - protected ClientBase(IPersistStreams persistStreams) - { - _persistStreams = persistStreams; - } - - protected IPersistStreams PersistStreams - { - get { return _persistStreams; } - } - - - /// - /// Observe commits from the sepecified checkpoint token. If the token is null, - /// all commits from the beginning will be observed. - /// - /// The checkpoint token. - /// An instance. - public abstract IObserveCommits ObserveFrom(Int64 checkpointToken = 0); - - /// - /// Observe commits from a bucket after the sepecified checkpoint token. If the token is null, - /// all commits from the beginning will be observed. - /// - /// The bucket id - /// The checkpoint token. - /// An instance. - public abstract IObserveCommits ObserveFromBucket(string bucketId, Int64 checkpointToken = 0); - } - - public interface IObserveCommits : IObservable, IDisposable - { - Task Start(); - - void PollNow(); - } - } - diff --git a/src/NEventStore.PollingClientExample/MainProgram.cs b/src/NEventStore.PollingClientExample/MainProgram.cs deleted file mode 100644 index 825768a7c..000000000 --- a/src/NEventStore.PollingClientExample/MainProgram.cs +++ /dev/null @@ -1,95 +0,0 @@ -using NEventStore.PollingClient; -using Microsoft.Extensions.Logging; - -namespace NEventStore.PollingClientExample -{ - internal static class MainProgram - { - private static void Main() - { - using var store = WireupEventStore(); - // append some commits to the EventStore - AppendToStream(store, "Stream1"); - AppendToStream(store, "Stream2"); - AppendToStream(store, "Stream1"); - - Console.WriteLine("--------------------------"); - Console.WriteLine("Starting PollingClient2..."); - Console.WriteLine(); - - // now test the polling client - Int64 checkpointToken = LoadCheckpoint(); - var client = new PollingClient2(store.Advanced, commit => - { - // Project the commit etc - Console.WriteLine("BucketId={0};StreamId={1};CommitSequence={2}", commit.BucketId, commit.StreamId, commit.CommitSequence); - // Track the most recent checkpoint - checkpointToken = commit.CheckpointToken; - return PollingClient2.HandlingResult.MoveToNext; - }, - waitInterval: 3000); - - client.StartFrom(checkpointToken); - Console.WriteLine("Wait for the Stream to end, then press any key to continue..."); - Console.ReadKey(); - client.Stop(); - - Console.WriteLine(); - Console.WriteLine("------------------------------"); - Console.WriteLine("Starting AsyncPollingClient..."); - Console.WriteLine(); - - checkpointToken = LoadCheckpoint(); - var observer = new LambdaAsyncObserver((commit, _) => - { - // Project the commit etc - Console.WriteLine("BucketId={0};StreamId={1};CommitSequence={2}", commit.BucketId, commit.StreamId, commit.CommitSequence); - // Track the most recent checkpoint - checkpointToken = commit.CheckpointToken; - return Task.FromResult(true); - }); - var asyncClient = new AsyncPollingClient(store.Advanced, observer, waitInterval: 3000, holeDetectionWaitInterval: 100); - asyncClient.Start(checkpointToken); - - Console.WriteLine("Press any key to continue..."); - Console.ReadKey(); - asyncClient.StopAsync() - .GetAwaiter().GetResult(); - } - - private static Int64 LoadCheckpoint() - { - // Load the checkpoint value from disk / local db/ etc - return 0; - } - - private static IStoreEvents WireupEventStore() - { - var loggerFactory = LoggerFactory.Create(logging => - { - logging - .AddConsole() - .AddDebug() - .SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); - }); - - return Wireup.Init() - .WithLoggerFactory(loggerFactory) - .UsingInMemoryPersistence() - .InitializeStorageEngine() -#if NET462 - .TrackPerformanceInstance("example") -#endif - .Build(); - } - - private static void AppendToStream(IStoreEvents store, string streamId) - { - using var stream = store.OpenStream(streamId); - var @event = new SomeDomainEvent { Value = "event" }; - - stream.Add(new EventMessage { Body = @event }); - stream.CommitChanges(Guid.NewGuid()); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.PollingClientExample/NEventStore.PollingClientExample.csproj b/src/NEventStore.PollingClientExample/NEventStore.PollingClientExample.csproj deleted file mode 100644 index a92a124ab..000000000 --- a/src/NEventStore.PollingClientExample/NEventStore.PollingClientExample.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - false - - exe - - Exe - - - - TRACE;DEBUG - - - - - - - - - - - - - - - - - - - diff --git a/src/NEventStore.PollingClientExample/Properties/ProjectAssemblyInfo.cs b/src/NEventStore.PollingClientExample/Properties/ProjectAssemblyInfo.cs deleted file mode 100644 index 33cbc2e74..000000000 --- a/src/NEventStore.PollingClientExample/Properties/ProjectAssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("NEventStore.Example")] -[assembly: AssemblyDescription("")] -[assembly: Guid("6fd621e0-9047-4449-b136-249383936d5c")] \ No newline at end of file diff --git a/src/NEventStore.PollingClientExample/SomeDomainEvent.cs b/src/NEventStore.PollingClientExample/SomeDomainEvent.cs deleted file mode 100644 index 96a47fffb..000000000 --- a/src/NEventStore.PollingClientExample/SomeDomainEvent.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NEventStore.PollingClientExample -{ - internal class SomeDomainEvent - { - public string? Value { get; set; } - } -} diff --git a/src/NEventStore.Serialization.Binary.Tests/NEventStore.Serialization.Binary.Core.Tests.csproj b/src/NEventStore.Serialization.Binary.Tests/NEventStore.Serialization.Binary.Core.Tests.csproj deleted file mode 100644 index d42777b05..000000000 --- a/src/NEventStore.Serialization.Binary.Tests/NEventStore.Serialization.Binary.Core.Tests.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - true - false - - exe - - NEventStore.Serialization.Binary.Tests - NEventStore.Serialization.Binary.Tests - true - - - - TRACE;DEBUG;NUNIT - - - NUNIT - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Binary.Tests/NUnitTestAdapterTestsDiscovery.cs deleted file mode 100644 index 25dac50c3..000000000 --- a/src/NEventStore.Serialization.Binary.Tests/NUnitTestAdapterTestsDiscovery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; - -namespace NEventStore.Serialization.Json.Tests -{ - /// - /// this is needed to allow NUnit test adapter to discover the tests, - /// if we do not have any class explicitly marked with the TestFixture attribute - /// all the assembly will be ignored (even if some class inherit from something which is - /// marked with the attribute in a reference assembly) - /// -#if NUNIT - [TestFixture] - public class NUnitTestAdapterTestsDiscovery - { - } -#endif -} diff --git a/src/NEventStore.Serialization.Binary.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Binary.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 186e9e034..000000000 --- a/src/NEventStore.Serialization.Binary.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.Binary.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("5bc73f44-a60e-4b2e-984d-81f56806cc79")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Binary.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Binary.Tests/SerializerFixture.cs deleted file mode 100644 index 380b191b2..000000000 --- a/src/NEventStore.Serialization.Binary.Tests/SerializerFixture.cs +++ /dev/null @@ -1,21 +0,0 @@ -using NEventStore.Serialization.Binary; - -// ReSharper disable CheckNamespace -namespace NEventStore.Serialization.AcceptanceTests -// ReSharper restore CheckNamespace -{ - public partial class SerializerFixture - { - public SerializerFixture() - { -#if NET8_0_OR_GREATER - // BinarySerializer: added EnableUnsafeBinaryFormatterSerialization in .csproj - // AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); -#endif -#pragma warning disable CS0618 // Type or member is obsolete - _createSerializer = () => - new BinarySerializer(); -#pragma warning restore CS0618 // Type or member is obsolete - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary/BinarySerializationWireupExtension.cs b/src/NEventStore.Serialization.Binary/BinarySerializationWireupExtension.cs deleted file mode 100644 index 7bb7bdfd6..000000000 --- a/src/NEventStore.Serialization.Binary/BinarySerializationWireupExtension.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Diagnostics; - -namespace NEventStore.Serialization.Binary -{ - /// - /// Binary serialization wire-up extensions. - /// - public static class BinarySerializationWireupExtension - { - /// - /// Use the MessagePack serializer. - /// - /// Wire-up to extend - /// Serialization Wire-up - [DebuggerStepThrough] - [Obsolete("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.")] - public static SerializationWireup UsingBinarySerialization(this PersistenceWireup wireup) - { -#if NET8_0_OR_GREATER - AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); -#endif - return wireup.UsingCustomSerialization(new BinarySerializer()); - } - } -} diff --git a/src/NEventStore.Serialization.Binary/BinarySerializer.cs b/src/NEventStore.Serialization.Binary/BinarySerializer.cs deleted file mode 100644 index 775b26339..000000000 --- a/src/NEventStore.Serialization.Binary/BinarySerializer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Runtime.Serialization.Formatters.Binary; -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore.Serialization.Binary -{ - /// - /// Delegates to to perform the actual serialization. - /// - [Obsolete("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.")] - public class BinarySerializer : ISerialize - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(BinarySerializer)); - private readonly BinaryFormatter _formatter = new(); - - /// - public virtual void Serialize(Stream output, T graph) where T : notnull - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.SerializingGraph, typeof(T)); - } - _formatter.Serialize(output, graph); - } - - /// - public virtual T Deserialize(Stream input) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.DeserializingStream, typeof(T)); - } - return (T)_formatter.Deserialize(input); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary/Messages.Designer.cs b/src/NEventStore.Serialization.Binary/Messages.Designer.cs deleted file mode 100644 index 80ff790b3..000000000 --- a/src/NEventStore.Serialization.Binary/Messages.Designer.cs +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NEventStore.Serialization.Binary { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.Binary.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - } -} diff --git a/src/NEventStore.Serialization.Binary/Messages.resx b/src/NEventStore.Serialization.Binary/Messages.resx deleted file mode 100644 index 971eea1e1..000000000 --- a/src/NEventStore.Serialization.Binary/Messages.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj b/src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj deleted file mode 100644 index 66b843a81..000000000 --- a/src/NEventStore.Serialization.Binary/NEventStore.Serialization.Binary.Core.csproj +++ /dev/null @@ -1,78 +0,0 @@ - - - netstandard2.0;$(ModernTargetFrameworks) - false - NEventStore.Serialization.Binary - NEventStore.Serialization.Binary - - - NEventStore.Serialization.Binary - NEventStore Binary Serializers - NEventStore Dev Team - http://neventstore.org - false - This package contains Bson serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. - events, event sourcing, cqrs, storage, persistence, database - - True - true - true - snupkg - true - true - True - True - NEventStore Dev Team - icon.png - Readme.md - https://github.com/NEventStore/NEventStore - git - license.txt - True - True - latest-recommended - - - TRACE;DEBUG - - - - True - \ - - - True - \ - - - True - \ - - - - - - - - Messages.resx - True - True - - - - - NEventStore.Serialization.Binary - Messages.Designer.cs - ResXFileCodeGenerator - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.Binary/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Binary/Properties/AssemblyInfo.cs deleted file mode 100644 index 49b637954..000000000 --- a/src/NEventStore.Serialization.Binary/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.Binary")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore.Serialization.Binary")] -[assembly: AssemblyCopyright("Copyright © 2011")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en-US")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("41CFA1C0-8B82-4F50-A758-056B556679F6")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson.Tests/KnownTypesTests.cs b/src/NEventStore.Serialization.Bson.Tests/KnownTypesTests.cs deleted file mode 100644 index 55e124547..000000000 --- a/src/NEventStore.Serialization.Bson.Tests/KnownTypesTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using Newtonsoft.Json; -using SerializerUnderTest = NEventStore.Serialization.Bson.BsonSerializer; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore.Serialization.Bson.Tests -{ -#if MSTEST - [TestClass] -#endif - public class when_using_the_default_bson_known_types - { - [Fact] - public void should_keep_event_message_lists_on_the_untyped_serializer_path() - { - var serializer = new InspectableBsonSerializer(); - - serializer.GetTypeNameHandlingFor(typeof(List)).Should().Be(TypeNameHandling.Auto); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_using_an_empty_bson_known_type_override - { - [Fact] - public void should_keep_the_default_known_types() - { - var serializer = new InspectableBsonSerializer([]); - - serializer.GetTypeNameHandlingFor(typeof(List)).Should().Be(TypeNameHandling.Auto); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_using_a_custom_bson_known_type_override - { - [Fact] - public void should_use_the_custom_known_type_set() - { - var serializer = new InspectableBsonSerializer(typeof(SimpleMessage)); - - serializer.GetTypeNameHandlingFor(typeof(SimpleMessage)).Should().Be(TypeNameHandling.Auto); - serializer.GetTypeNameHandlingFor(typeof(List)).Should().Be(TypeNameHandling.All); - } - } - - internal sealed class InspectableBsonSerializer(params Type[]? knownTypes) : SerializerUnderTest(knownTypes) - { - public TypeNameHandling GetTypeNameHandlingFor(Type type) - { - return GetSerializer(type).TypeNameHandling; - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Serialization.Bson.Tests/NEventStore.Serialization.Bson.Core.Tests.csproj b/src/NEventStore.Serialization.Bson.Tests/NEventStore.Serialization.Bson.Core.Tests.csproj deleted file mode 100644 index 35bf85823..000000000 --- a/src/NEventStore.Serialization.Bson.Tests/NEventStore.Serialization.Bson.Core.Tests.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - false - - exe - - NEventStore.Serialization.Bson.Tests - NEventStore.Serialization.Bson.Tests - true - - - - TRACE;DEBUG;NUNIT - - - NUNIT - - - - - - - - - - - - - - - - - - - - - diff --git a/src/NEventStore.Serialization.Bson.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Bson.Tests/NUnitTestAdapterTestsDiscovery.cs deleted file mode 100644 index e0ea056df..000000000 --- a/src/NEventStore.Serialization.Bson.Tests/NUnitTestAdapterTestsDiscovery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; - -namespace NEventStore.Serialization.Bson.Tests -{ - /// - /// this is needed to allow NUnit test adapter to discover the tests, - /// if we do not have any class explicitly marked with the TestFixture attribute - /// all the assembly will be ignored (even if some class inherit from something which is - /// marked with the attribute in a reference assembly) - /// -#if NUNIT - [TestFixture] - public class NUnitTestAdapterTestsDiscovery - { - } -#endif -} diff --git a/src/NEventStore.Serialization.Bson.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Bson.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 914757de1..000000000 --- a/src/NEventStore.Serialization.Bson.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.Bson.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("94518417-F5DE-44D7-B20B-0DD8BB86DC6C")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Bson.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Bson.Tests/SerializerFixture.cs deleted file mode 100644 index 3d9a2028a..000000000 --- a/src/NEventStore.Serialization.Bson.Tests/SerializerFixture.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ReSharper disable CheckNamespace -namespace NEventStore.Serialization.AcceptanceTests -// ReSharper restore CheckNamespace -{ - using NEventStore.Serialization.Bson; - - public partial class SerializerFixture - { - public SerializerFixture() - { - _createSerializer = () => - new BsonSerializer(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson/BsonSerializationWireupExtension.cs b/src/NEventStore.Serialization.Bson/BsonSerializationWireupExtension.cs deleted file mode 100644 index 8b0bd99fb..000000000 --- a/src/NEventStore.Serialization.Bson/BsonSerializationWireupExtension.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NEventStore.Serialization.Bson -{ - /// - /// Bson serialization wire-up extensions. - /// - public static class BsonSerializationWireupExtension - { - /// - /// Specify we want to use Bson serialization. - /// - public static SerializationWireup UsingBsonSerialization(this PersistenceWireup wireup) - { - return wireup.UsingCustomSerialization(new BsonSerializer()); - } - } -} diff --git a/src/NEventStore.Serialization.Bson/BsonSerializer.cs b/src/NEventStore.Serialization.Bson/BsonSerializer.cs deleted file mode 100644 index 0eba01e20..000000000 --- a/src/NEventStore.Serialization.Bson/BsonSerializer.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.Collections; -using NEventStore.Logging; -using Newtonsoft.Json.Bson; -using Newtonsoft.Json; -using Microsoft.Extensions.Logging; - -namespace NEventStore.Serialization.Bson -{ - /// - /// Represents a BSON serializer. - /// - public class BsonSerializer : ISerialize - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(BsonSerializer)); - - private static readonly Type[] DefaultKnownTypes = [typeof(List), typeof(Dictionary)]; - - private readonly HashSet _knownTypes; - - private readonly JsonSerializer _typedSerializer = new() - { - TypeNameHandling = TypeNameHandling.All, - DefaultValueHandling = DefaultValueHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore - }; - - private readonly JsonSerializer _untypedSerializer = new() - { - TypeNameHandling = TypeNameHandling.Auto, - DefaultValueHandling = DefaultValueHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore - }; - - /// - /// Initializes a new instance of the BsonSerializer class. - /// - public BsonSerializer(params Type[]? knownTypes) - { - if (knownTypes?.Length == 0) - { - knownTypes = null; - } - - // BSON uses the same typed/untyped serializer split as JSON. Normalize known types once - // at construction time so each serialize/deserialize call can use O(1) membership - // checks without changing payload shape or custom known-type semantics. - _knownTypes = new HashSet(knownTypes ?? DefaultKnownTypes); - - if (Logger.IsEnabled(LogLevel.Debug)) - { - foreach (var type in _knownTypes) - { - Logger.LogDebug(Messages.RegisteringKnownType, type); - } - } - } - - /// - public virtual void Serialize(Stream output, T graph) where T: notnull - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.SerializingGraph, typeof(T)); - } - using var writer = new BsonDataWriter(output) { DateTimeKindHandling = DateTimeKind.Utc }; - Serialize(writer, graph); - } - - /// - public virtual T? Deserialize(Stream input) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.DeserializingStream, typeof(T)); - } - using var reader = new BsonDataReader(input, IsArray(typeof(T)), DateTimeKind.Utc); - return Deserialize(reader); - } - - /// - /// Serialize an object to a JsonWriter. - /// - protected virtual void Serialize(JsonWriter writer, object graph) - { - GetSerializer(graph.GetType()).Serialize(writer, graph); - } - - /// - /// Deserialize an object from a JsonReader. - /// - protected virtual T? Deserialize(JsonReader reader) - { - Type type = typeof(T); - return (T?)GetSerializer(type).Deserialize(reader, type); - } - - /// - /// Get the serializer for the given type. - /// - protected virtual Newtonsoft.Json.JsonSerializer GetSerializer(Type typeToSerialize) - { - if (_knownTypes.Contains(typeToSerialize)) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.UsingUntypedSerializer, typeToSerialize); - } - return _untypedSerializer; - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.UsingTypedSerializer, typeToSerialize); - } - return _typedSerializer; - } - - private static bool IsArray(Type type) - { - bool array = typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IDictionary).IsAssignableFrom(type); - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.TypeIsArray, type, array); - } - - return array; - } - } -} diff --git a/src/NEventStore.Serialization.Bson/Messages.Designer.cs b/src/NEventStore.Serialization.Bson/Messages.Designer.cs deleted file mode 100644 index ec95a95da..000000000 --- a/src/NEventStore.Serialization.Bson/Messages.Designer.cs +++ /dev/null @@ -1,117 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NEventStore.Serialization.Bson { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.Bson.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering type '{0}' as a known type.. - /// - internal static string RegisteringKnownType { - get { - return ResourceManager.GetString("RegisteringKnownType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Objects of type '{0}' are considered to be an array: '{1}'.. - /// - internal static string TypeIsArray { - get { - return ResourceManager.GetString("TypeIsArray", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type.. - /// - internal static string UsingTypedSerializer { - get { - return ResourceManager.GetString("UsingTypedSerializer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using an untyped serializer for the known type.. - /// - internal static string UsingUntypedSerializer { - get { - return ResourceManager.GetString("UsingUntypedSerializer", resourceCulture); - } - } - } -} diff --git a/src/NEventStore.Serialization.Bson/Messages.resx b/src/NEventStore.Serialization.Bson/Messages.resx deleted file mode 100644 index 390f70808..000000000 --- a/src/NEventStore.Serialization.Bson/Messages.resx +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - - - Registering type '{0}' as a known type. - - - Objects of type '{0}' are considered to be an array: '{1}'. - - - The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type. - - - The object to be serialized is of type '{0}'. Using an untyped serializer for the known type. - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj b/src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj deleted file mode 100644 index 17dbd40ae..000000000 --- a/src/NEventStore.Serialization.Bson/NEventStore.Serialization.Bson.Core.csproj +++ /dev/null @@ -1,77 +0,0 @@ - - - netstandard2.0;$(ModernTargetFrameworks) - false - NEventStore.Serialization.Bson - NEventStore.Serialization.Bson - - - NEventStore.Serialization.Bson - NEventStore Bson Serializers - NEventStore Dev Team - http://neventstore.org - false - This package contains Bson serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. - events, event sourcing, cqrs, storage, persistence, database - - True - true - true - snupkg - true - true - True - True - NEventStore Dev Team - icon.png - Readme.md - https://github.com/NEventStore/NEventStore - git - license.txt - True - True - latest-recommended - - - TRACE;DEBUG - - - - True - \ - - - True - \ - - - True - \ - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - True - True - Messages.resx - - - - - ResXFileCodeGenerator - Messages.Designer.cs - NEventStore.Serialization.Bson - - - diff --git a/src/NEventStore.Serialization.Bson/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Bson/Properties/AssemblyInfo.cs deleted file mode 100644 index 25ce91a4d..000000000 --- a/src/NEventStore.Serialization.Bson/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.Bson")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore.Serialization.Bson")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en-US")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f5bfbe96-2008-4d10-9716-dd7e1bd6cb5e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/NEventStore.Serialization.Gzip.Tests/NEventStore.Serialization.GZip.Core.Tests.csproj b/src/NEventStore.Serialization.Gzip.Tests/NEventStore.Serialization.GZip.Core.Tests.csproj deleted file mode 100644 index 4498dad5d..000000000 --- a/src/NEventStore.Serialization.Gzip.Tests/NEventStore.Serialization.GZip.Core.Tests.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - true - false - - exe - - NEventStore.Serialization.GZip.Tests - NEventStore.Serialization.GZip.Tests - true - - - - TRACE;DEBUG;NUNIT - - - NUNIT - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.Gzip.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Gzip.Tests/NUnitTestAdapterTestsDiscovery.cs deleted file mode 100644 index b8ee80d9f..000000000 --- a/src/NEventStore.Serialization.Gzip.Tests/NUnitTestAdapterTestsDiscovery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; - -namespace NEventStore.Serialization.GZip.Tests -{ - /// - /// this is needed to allow NUnit test adapter to discover the tests, - /// if we do not have any class explicitly marked with the TestFixture attribute - /// all the assembly will be ignored (even if some class inherit from something which is - /// marked with the attribute in a reference assembly) - /// -#if NUNIT - [TestFixture] - public class NUnitTestAdapterTestsDiscovery - { - } -#endif -} diff --git a/src/NEventStore.Serialization.Gzip.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Gzip.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index cddbe103b..000000000 --- a/src/NEventStore.Serialization.Gzip.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.Gzip.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("5BB89C20-A481-4A15-9478-C0F69E419239")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Gzip.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Gzip.Tests/SerializerFixture.cs deleted file mode 100644 index e91ca087c..000000000 --- a/src/NEventStore.Serialization.Gzip.Tests/SerializerFixture.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ReSharper disable CheckNamespace -using NEventStore.Serialization.Binary; - -namespace NEventStore.Serialization.AcceptanceTests -// ReSharper restore CheckNamespace -{ - public partial class SerializerFixture - { - public SerializerFixture() - { -#if NET8_0_OR_GREATER - // BinarySerializer: added EnableUnsafeBinaryFormatterSerialization in .csproj - // AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); -#endif -#pragma warning disable CS0618 // Type or member is obsolete - _createSerializer = () => - new GzipSerializer(new BinarySerializer()); -#pragma warning restore CS0618 // Type or member is obsolete - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json.Tests/KnownTypesTests.cs b/src/NEventStore.Serialization.Json.Tests/KnownTypesTests.cs deleted file mode 100644 index 6d66abbe6..000000000 --- a/src/NEventStore.Serialization.Json.Tests/KnownTypesTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using Newtonsoft.Json; -using SerializerUnderTest = NEventStore.Serialization.Json.JsonSerializer; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore.Serialization.Json.Tests -{ -#if MSTEST - [TestClass] -#endif - public class when_using_the_default_json_known_types - { - [Fact] - public void should_keep_event_message_lists_on_the_untyped_serializer_path() - { - var serializer = new InspectableJsonSerializer(null); - - serializer.GetTypeNameHandlingFor(typeof(List)).Should().Be(TypeNameHandling.Auto); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_using_an_empty_json_known_type_override - { - [Fact] - public void should_keep_the_default_known_types() - { - var serializer = new InspectableJsonSerializer(null, []); - - serializer.GetTypeNameHandlingFor(typeof(List)).Should().Be(TypeNameHandling.Auto); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_using_a_custom_json_known_type_override - { - [Fact] - public void should_use_the_custom_known_type_set() - { - var serializer = new InspectableJsonSerializer(null, typeof(SimpleMessage)); - - serializer.GetTypeNameHandlingFor(typeof(SimpleMessage)).Should().Be(TypeNameHandling.Auto); - serializer.GetTypeNameHandlingFor(typeof(List)).Should().Be(TypeNameHandling.All); - } - } - - internal sealed class InspectableJsonSerializer(JsonSerializerSettings? settings, params Type[]? knownTypes) - : SerializerUnderTest(settings, knownTypes) - { - public TypeNameHandling GetTypeNameHandlingFor(Type type) - { - return GetSerializer(type).TypeNameHandling; - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Serialization.Json.Tests/NEventStore.Serialization.Json.Core.Tests.csproj b/src/NEventStore.Serialization.Json.Tests/NEventStore.Serialization.Json.Core.Tests.csproj deleted file mode 100644 index e3e70cc4a..000000000 --- a/src/NEventStore.Serialization.Json.Tests/NEventStore.Serialization.Json.Core.Tests.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - false - - exe - - NEventStore.Serialization.Json.Tests - NEventStore.Serialization.Json.Tests - true - - - - TRACE;DEBUG;NUNIT - - - NUNIT - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/NEventStore.Serialization.Json.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Json.Tests/NUnitTestAdapterTestsDiscovery.cs deleted file mode 100644 index e6f66c26f..000000000 --- a/src/NEventStore.Serialization.Json.Tests/NUnitTestAdapterTestsDiscovery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; - -namespace NEventStore.Serialization.Json.Tests -{ - /// - /// this is needed to allow NUnit test adapter to discover the tests, - /// if we do not have any class explicitly marked with the TestFixture attribute - /// all the assembly will be ignored (even if some class inherit from something which is - /// marked with the attribute in a reference assembly) - /// -#if NUNIT - [TestFixture] - public class NUnitTestAdapterTestsDiscovery - { - } -#endif -} diff --git a/src/NEventStore.Serialization.Json.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Json.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index f8bf36ef2..000000000 --- a/src/NEventStore.Serialization.Json.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.Json.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9775F040-BA9C-48C1-BB16-1A1D6B86A3F1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Json.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Json.Tests/SerializerFixture.cs deleted file mode 100644 index 040a7c2c6..000000000 --- a/src/NEventStore.Serialization.Json.Tests/SerializerFixture.cs +++ /dev/null @@ -1,15 +0,0 @@ -using NEventStore.Serialization.Json; - -// ReSharper disable CheckNamespace -namespace NEventStore.Serialization.AcceptanceTests -// ReSharper restore CheckNamespace -{ - public partial class SerializerFixture - { - public SerializerFixture() - { - _createSerializer = () => - new JsonSerializer(null); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json.Tests/SystemTextJsonCompatibilityTests.cs b/src/NEventStore.Serialization.Json.Tests/SystemTextJsonCompatibilityTests.cs deleted file mode 100644 index c427d1cb6..000000000 --- a/src/NEventStore.Serialization.Json.Tests/SystemTextJsonCompatibilityTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using NEventStore.Serialization.SystemTextJson; - -namespace NEventStore.Serialization.Json.Tests -{ - public class when_newtonsoft_deserializes_system_text_json_event_messages : SpecificationBase - { - private readonly List _messages = - [ - new EventMessage { Body = "some value" }, - new EventMessage { Body = 42 }, - new EventMessage { Body = new SimpleMessage().Populate() }, - ]; - - private List? _deserialized; - - protected override void Because() - { - var serialized = new SystemTextJsonSerializer().Serialize(_messages); - _deserialized = new JsonSerializer(null).Deserialize>(serialized); - } - - [Fact] - public void should_deserialize_complex_event_body_types() - { - _deserialized!.Last().Body.Should().BeOfType(); - } - } - - public class when_newtonsoft_deserializes_system_text_json_headers : SpecificationBase - { - private readonly Dictionary _headers = new() - { - ["HeaderKey"] = "SomeValue", - ["NumericKey"] = 42, - ["ComplexKey"] = new SimpleMessage().Populate(), - }; - - private Dictionary? _deserialized; - - protected override void Because() - { - var serialized = new SystemTextJsonSerializer().Serialize(_headers); - _deserialized = new JsonSerializer(null).Deserialize>(serialized); - } - - [Fact] - public void should_deserialize_complex_header_types() - { - _deserialized!["ComplexKey"].Should().BeOfType(); - } - } - - public class when_newtonsoft_deserializes_system_text_json_snapshot_payloads : SpecificationBase - { - private readonly Snapshot _snapshot = new Snapshot(Guid.NewGuid().ToString(), 42, new Dictionary> - { - ["values"] = [1, 2, 3], - }); - - private Snapshot? _deserialized; - - protected override void Because() - { - var serialized = new SystemTextJsonSerializer().Serialize(_snapshot); - _deserialized = new JsonSerializer(null).Deserialize(serialized); - } - - [Fact] - public void should_deserialize_payload_type() - { - _deserialized!.Payload.Should().BeOfType(_snapshot.Payload.GetType()); - } - - [Fact] - public void should_deserialize_payload_contents() - { - _deserialized!.Payload.Should().BeEquivalentTo(_snapshot.Payload); - } - } -} diff --git a/src/NEventStore.Serialization.Json/JsonSerializationWireupExtension.cs b/src/NEventStore.Serialization.Json/JsonSerializationWireupExtension.cs deleted file mode 100644 index 503ccaa41..000000000 --- a/src/NEventStore.Serialization.Json/JsonSerializationWireupExtension.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Newtonsoft.Json; - -namespace NEventStore.Serialization.Json -{ - /// - /// Newtonsoft Json serialization wire-up extensions. - /// - public static class JsonSerializationWireupExtension - { - /// - /// Specify we want to use Json serialization using Newtonsoft.Json - /// - /// The persistence wire-up. - /// - /// Allows to customize some Serializer options, however some of them will - /// be under control of this specific implementation and will be overwritten no matter - /// what the user specifies: - /// - TypeNameHandling = TypeNameHandling.All - /// - DefaultValueHandling = DefaultValueHandling.Ignore - /// - NullValueHandling = NullValueHandling.Ignore - /// - public static SerializationWireup UsingJsonSerialization( - this PersistenceWireup wireup, - JsonSerializerSettings? jsonSerializerSettings = null) - { - return wireup.UsingCustomSerialization(new JsonSerializer(jsonSerializerSettings)); - } - } -} diff --git a/src/NEventStore.Serialization.Json/JsonSerializer.cs b/src/NEventStore.Serialization.Json/JsonSerializer.cs deleted file mode 100644 index 4d038fd0c..000000000 --- a/src/NEventStore.Serialization.Json/JsonSerializer.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System.Text; -using Newtonsoft.Json; -using NEventStore.Logging; -using Microsoft.Extensions.Logging; - -namespace NEventStore.Serialization.Json -{ - /// - /// The Newtonsoft JSON serializer. - /// - public class JsonSerializer : ISerialize - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(JsonSerializer)); - private static readonly Type[] DefaultKnownTypes = [typeof(List), typeof(Dictionary)]; - - private readonly HashSet _knownTypes; - - private readonly Newtonsoft.Json.JsonSerializer _typedSerializer; - - private readonly Newtonsoft.Json.JsonSerializer _untypedSerializer; - - /// - /// NEventStore Json Serialization using Newtonsoft.Json - /// - /// Allows to configure some Json serialization options, - /// some of them will be overwritten given the values passed to parameter - /// - /// Every Type specified here will be serialized with: - /// - TypeNameHandling = TypeNameHandling.Auto - /// - DefaultValueHandling = DefaultValueHandling.Ignore - /// - NullValueHandling = NullValueHandling.Ignore - /// Every other type will be serialized with: - /// - TypeNameHandling = TypeNameHandling.All - /// - DefaultValueHandling = DefaultValueHandling.Ignore - /// - NullValueHandling = NullValueHandling.Ignore - /// - public JsonSerializer(JsonSerializerSettings? jsonSerializerSettings, params Type[]? knownTypes) - { - if (knownTypes?.Length == 0) - { - knownTypes = null; - } - - // Serializer selection happens on every serialize/deserialize call. Keep the known - // type contract unchanged, but normalize it once into a HashSet so modern package - // assets and compatibility assets avoid repeated enumerable scans in the hot path. - _knownTypes = new HashSet(knownTypes ?? DefaultKnownTypes); - - _typedSerializer = Newtonsoft.Json.JsonSerializer.Create(jsonSerializerSettings); - _typedSerializer.TypeNameHandling = TypeNameHandling.All; - _typedSerializer.DefaultValueHandling = DefaultValueHandling.Ignore; - _typedSerializer.NullValueHandling = NullValueHandling.Ignore; - - _untypedSerializer = Newtonsoft.Json.JsonSerializer.Create(jsonSerializerSettings); - _untypedSerializer.TypeNameHandling = TypeNameHandling.Auto; - _untypedSerializer.DefaultValueHandling = DefaultValueHandling.Ignore; - _untypedSerializer.NullValueHandling = NullValueHandling.Ignore; - - if (Logger.IsEnabled(LogLevel.Debug)) - { - foreach (var type in _knownTypes) - { - Logger.LogDebug(Messages.RegisteringKnownType, type); - } - } - } - - /// - public virtual void Serialize(Stream output, T graph) where T: notnull - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.SerializingGraph, typeof(T)); - } - using var streamWriter = new StreamWriter(output, Encoding.UTF8); - using var jsonTextWriter = new JsonTextWriter(streamWriter); - Serialize(jsonTextWriter, graph); - } - - /// - public virtual T? Deserialize(Stream input) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.DeserializingStream, typeof(T)); - } - using var streamReader = new StreamReader(input, Encoding.UTF8); - using var jsonTextReader = new JsonTextReader(streamReader); - return Deserialize(jsonTextReader); - } - - /// - /// Serialize an object to a JsonWriter. - /// - protected virtual void Serialize(JsonWriter writer, object graph) - { - GetSerializer(graph.GetType()).Serialize(writer, graph); - } - - /// - /// Deserialize an object from a JsonReader. - /// - protected virtual T? Deserialize(JsonReader reader) - { - Type type = typeof(T); - return (T?)GetSerializer(type).Deserialize(reader, type); - } - - /// - /// Get the serializer for the given type. - /// - protected virtual Newtonsoft.Json.JsonSerializer GetSerializer(Type typeToSerialize) - { - if (_knownTypes.Contains(typeToSerialize)) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.UsingUntypedSerializer, typeToSerialize); - } - return _untypedSerializer; - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.UsingTypedSerializer, typeToSerialize); - } - return _typedSerializer; - } - } -} diff --git a/src/NEventStore.Serialization.Json/Messages.Designer.cs b/src/NEventStore.Serialization.Json/Messages.Designer.cs deleted file mode 100644 index 533c6643a..000000000 --- a/src/NEventStore.Serialization.Json/Messages.Designer.cs +++ /dev/null @@ -1,108 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NEventStore.Serialization.Json { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.Json.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering type '{0}' as a known type.. - /// - internal static string RegisteringKnownType { - get { - return ResourceManager.GetString("RegisteringKnownType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type.. - /// - internal static string UsingTypedSerializer { - get { - return ResourceManager.GetString("UsingTypedSerializer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object to be serialized is of type '{0}'. Using an untyped serializer for the known type.. - /// - internal static string UsingUntypedSerializer { - get { - return ResourceManager.GetString("UsingUntypedSerializer", resourceCulture); - } - } - } -} diff --git a/src/NEventStore.Serialization.Json/Messages.resx b/src/NEventStore.Serialization.Json/Messages.resx deleted file mode 100644 index 34a0c4cfe..000000000 --- a/src/NEventStore.Serialization.Json/Messages.resx +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - - - Registering type '{0}' as a known type. - - - The object to be serialized is of type '{0}'. Using a typed serializer for the unknown type. - - - The object to be serialized is of type '{0}'. Using an untyped serializer for the known type. - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj b/src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj deleted file mode 100644 index 96db461ed..000000000 --- a/src/NEventStore.Serialization.Json/NEventStore.Serialization.Json.Core.csproj +++ /dev/null @@ -1,76 +0,0 @@ - - - netstandard2.0;$(ModernTargetFrameworks) - false - NEventStore.Serialization.Json - NEventStore.Serialization.Json - - - NEventStore.Serialization.Json - NEventStore Json Serializers - NEventStore Dev Team - http://neventstore.org - false - This package contains Json serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. - events, event sourcing, cqrs, storage, persistence, database - - True - true - true - snupkg - true - true - True - True - NEventStore Dev Team - icon.png - README.md - https://github.com/NEventStore/NEventStore - git - license.txt - True - True - latest-recommended - - - TRACE;DEBUG - - - - True - \ - - - True - \ - - - True - \ - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - True - True - Messages.resx - - - - - ResXFileCodeGenerator - Messages.Designer.cs - NEventStore.Serialization.Json - - - diff --git a/src/NEventStore.Serialization.Json/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Json/Properties/AssemblyInfo.cs deleted file mode 100644 index 5ebcc09c5..000000000 --- a/src/NEventStore.Serialization.Json/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.Json")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore.Serialization.Json")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en-US")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7bbb9172-7b2c-4854-9b59-952b54122632")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/NEventStore.Serialization.Json/README.md b/src/NEventStore.Serialization.Json/README.md deleted file mode 100644 index 0635a4e76..000000000 --- a/src/NEventStore.Serialization.Json/README.md +++ /dev/null @@ -1,168 +0,0 @@ -# NEventStore.Serialization.Json - -`NEventStore.Serialization.Json` is the Newtonsoft.Json serializer for NEventStore. It is the compatibility contract that the System.Text.Json serializer follows so either package can be used for the same persisted JSON. - -## Wireup - -```csharp -var store = Wireup.Init() - .UsingInMemoryPersistence() - .UsingJsonSerialization() - .Build(); -``` - -Custom Newtonsoft settings can be supplied, and root known types can be overridden: - -```csharp -var serializer = new JsonSerializer( - new JsonSerializerSettings(), - typeof(List), - typeof(Dictionary)); -``` - -The default known types are: - -- `List` -- `Dictionary` - -Passing `null` or an empty known-type array keeps those defaults. Passing a non-empty array replaces them. - -## Implementation Model - -The serializer has two internal Newtonsoft serializers: - -- Untyped serializer for known root types: `TypeNameHandling.Auto`, `DefaultValueHandling.Ignore`, `NullValueHandling.Ignore`. -- Typed serializer for every other root type: `TypeNameHandling.All`, `DefaultValueHandling.Ignore`, `NullValueHandling.Ignore`. - -Known root types are intentionally written without a root `$type` wrapper. Their polymorphic `object` members still receive `$type` metadata when the runtime value is not assignable from the declared type. This is what preserves event body, header, and snapshot payload types. - -Unknown root types are written with root `$type` metadata. This keeps snapshots and custom serialized root objects self-describing. - -## Compatibility Contract - -The JSON contract is based on Newtonsoft.Json metadata names: - -- `$type` stores an assembly-qualified CLR type name. -- `$values` stores array contents when the typed value itself is an array or collection. -- Null properties and default values are omitted. -- Known root types are read and written without root `$type` metadata. -- Polymorphic `object` values are read and written with `$type` metadata when needed. - -The System.Text.Json serializer emits and reads the same metadata contract for the NEventStore cases that require polymorphism. The two serializers are wire-compatible; they are not required to produce byte-for-byte identical JSON when static property types already provide enough type information. - -## Serialized Data Examples - -Assembly versions are shortened in these examples. Real payloads contain full assembly-qualified names. - -### Event Messages - -Source object: - -```csharp -var messages = new List -{ - new EventMessage { Body = "some value" }, - new EventMessage { Body = 42 }, - new EventMessage { Body = new SimpleMessage { Count = 1234, Value = "Hello" } } -}; -``` - -JSON shape: - -```json -[ - { - "Headers": {}, - "Body": "some value" - }, - { - "Headers": {}, - "Body": 42 - }, - { - "Headers": {}, - "Body": { - "$type": "NEventStore.Persistence.AcceptanceTests.SimpleMessage, NEventStore.Persistence.AcceptanceTests", - "Id": "00000000-0000-0000-0000-000000000000", - "Created": "0001-01-01T00:00:00", - "Value": "Hello", - "Count": 1234, - "Contents": [] - } - } -] -``` - -`List` is a known root type, so the array has no root `$type`. The third `Body` is declared as `object`, so it has `$type` metadata. - -### Commit Headers - -Source object: - -```csharp -var headers = new Dictionary -{ - ["HeaderKey"] = "SomeValue", - ["NumericKey"] = 42, - ["ComplexKey"] = new SimpleMessage { Count = 1234 } -}; -``` - -JSON shape: - -```json -{ - "HeaderKey": "SomeValue", - "NumericKey": 42, - "ComplexKey": { - "$type": "NEventStore.Persistence.AcceptanceTests.SimpleMessage, NEventStore.Persistence.AcceptanceTests", - "Id": "00000000-0000-0000-0000-000000000000", - "Created": "0001-01-01T00:00:00", - "Count": 1234, - "Contents": [] - } -} -``` - -`Dictionary` is a known root type, so the dictionary itself has no root `$type`. Complex values are typed. - -### Snapshot Payloads - -Source object: - -```csharp -var snapshot = new Snapshot("stream-1", 42, new Dictionary> -{ - ["values"] = [1, 2, 3] -}); -``` - -JSON shape: - -```json -{ - "$type": "NEventStore.Snapshot, NEventStore", - "BucketId": "default", - "StreamId": "stream-1", - "StreamRevision": 42, - "Payload": { - "$type": "System.Collections.Generic.Dictionary`2[[System.String,...],[System.Collections.Generic.List`1[[System.Int32,...]],...]], ...", - "values": { - "$type": "System.Collections.Generic.List`1[[System.Int32,...]], ...", - "$values": [1, 2, 3] - } - } -} -``` - -`Snapshot` is not a known root type, so the root has `$type`. Its `Payload` property is declared as `object`, so the dictionary payload is typed. The nested list uses `$values` because typed collections are represented as objects with metadata plus values. - -## Swap Verification - -The test suite verifies: - -- Newtonsoft round-trips through the shared serialization acceptance tests. -- Default and custom known-type selection. -- System.Text.Json output can be read by Newtonsoft for event messages, headers, and snapshot payloads. - -The mirrored System.Text.Json tests verify the reverse direction. diff --git a/src/NEventStore.Serialization.MsgPack.Tests/NEventStore.Serialization.MsgPack.Core.Tests.csproj b/src/NEventStore.Serialization.MsgPack.Tests/NEventStore.Serialization.MsgPack.Core.Tests.csproj deleted file mode 100644 index 3e39bc526..000000000 --- a/src/NEventStore.Serialization.MsgPack.Tests/NEventStore.Serialization.MsgPack.Core.Tests.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - false - - exe - - NEventStore.Serialization.MsgPack.Tests - NEventStore.Serialization.MsgPack.Tests - true - - - - TRACE;DEBUG;NUNIT - - - NUNIT - - - - - - - - - - - - - - - - - - - - - diff --git a/src/NEventStore.Serialization.MsgPack.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.MsgPack.Tests/NUnitTestAdapterTestsDiscovery.cs deleted file mode 100644 index e78b055bf..000000000 --- a/src/NEventStore.Serialization.MsgPack.Tests/NUnitTestAdapterTestsDiscovery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; - -namespace NEventStore.Serialization.MsgPack.Tests -{ - /// - /// this is needed to allow NUnit test adapter to discover the tests, - /// if we do not have any class explicitly marked with the TestFixture attribute - /// all the assembly will be ignored (even if some class inherit from something which is - /// marked with the attribute in a reference assembly) - /// -#if NUNIT - [TestFixture] - public class NUnitTestAdapterTestsDiscovery - { - } -#endif -} diff --git a/src/NEventStore.Serialization.MsgPack.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.MsgPack.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 41a702b36..000000000 --- a/src/NEventStore.Serialization.MsgPack.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.MsgPack.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("646D1249-63B9-46AD-8F9D-BFC58D3AC6F1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.MsgPack.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.MsgPack.Tests/SerializerFixture.cs deleted file mode 100644 index 76b30107f..000000000 --- a/src/NEventStore.Serialization.MsgPack.Tests/SerializerFixture.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ReSharper disable CheckNamespace -namespace NEventStore.Serialization.AcceptanceTests -// ReSharper restore CheckNamespace -{ - using NEventStore.Serialization.MsgPack; - - public partial class SerializerFixture - { - public SerializerFixture() - { - _createSerializer = () => - new MsgPackSerializer(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Serialization.MsgPack/Messages.Designer.cs b/src/NEventStore.Serialization.MsgPack/Messages.Designer.cs deleted file mode 100644 index 44d7751fb..000000000 --- a/src/NEventStore.Serialization.MsgPack/Messages.Designer.cs +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NEventStore.Serialization.MsgPack { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.MsgPack.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - } -} diff --git a/src/NEventStore.Serialization.MsgPack/Messages.resx b/src/NEventStore.Serialization.MsgPack/Messages.resx deleted file mode 100644 index 971eea1e1..000000000 --- a/src/NEventStore.Serialization.MsgPack/Messages.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.MsgPack/MsgPackSerializationWireupExtension.cs b/src/NEventStore.Serialization.MsgPack/MsgPackSerializationWireupExtension.cs deleted file mode 100644 index e26d20f1a..000000000 --- a/src/NEventStore.Serialization.MsgPack/MsgPackSerializationWireupExtension.cs +++ /dev/null @@ -1,20 +0,0 @@ -using MessagePack; -using System.Diagnostics; - -namespace NEventStore.Serialization.MsgPack -{ - /// - /// MsgPack serialization wire-up extensions. - /// - public static class MsgPackSerializationWireupExtension - { - /// - /// Use the MessagePack serializer. - /// - /// Wire-up to extend - /// MsgPack serialization options - /// Serialization Wire-up - [DebuggerStepThrough] - public static SerializationWireup UsingMsgPackSerialization(this PersistenceWireup wireup, MessagePackSerializerOptions? option = null) => wireup.UsingCustomSerialization(new MsgPackSerializer(option)); - } -} diff --git a/src/NEventStore.Serialization.MsgPack/MsgPackSerializer.cs b/src/NEventStore.Serialization.MsgPack/MsgPackSerializer.cs deleted file mode 100644 index fa61054db..000000000 --- a/src/NEventStore.Serialization.MsgPack/MsgPackSerializer.cs +++ /dev/null @@ -1,58 +0,0 @@ -using MessagePack; -using MessagePack.Resolvers; -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore.Serialization.MsgPack -{ - /// - /// MsgPack serializer - /// - public class MsgPackSerializer : ISerialize - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(MsgPackSerializer)); - /// - /// Serializer options - /// - private readonly MessagePackSerializerOptions _options; - - /// - /// Initializes a new instance. - /// - /// MsgPack Options. - public MsgPackSerializer(MessagePackSerializerOptions? options = null) - { - _options = options ?? new MessagePackSerializerOptions(TypelessContractlessStandardResolver.Instance); - } - - /// - /// Serializes an object. - /// - /// Object type - /// Output stream - /// Object to deserialize. - public virtual void Serialize(Stream output, T graph) where T : notnull - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.SerializingGraph, typeof(T)); - } - MessagePackSerializer.Serialize(output, graph, _options); - } - - /// - /// Deserializes an object from stream. - /// - /// Object type. - /// Stream input - /// Deserialized object - public virtual T Deserialize(Stream input) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.DeserializingStream, typeof(T)); - } - return MessagePackSerializer.Deserialize(input, _options); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj b/src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj deleted file mode 100644 index 1c1067de2..000000000 --- a/src/NEventStore.Serialization.MsgPack/NEventStore.Serialization.MsgPack.Core.csproj +++ /dev/null @@ -1,76 +0,0 @@ - - - netstandard2.0;$(ModernTargetFrameworks) - false - NEventStore.Serialization.MsgPack - NEventStore.Serialization.MsgPack - - - NEventStore.Serialization.MsgPack - NEventStore MsgPack Serializers - NEventStore Dev Team - http://neventstore.org - false - This package contains Bson serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. - events, event sourcing, cqrs, storage, persistence, database - - True - true - true - snupkg - true - true - True - True - NEventStore Dev Team - icon.png - Readme.md - https://github.com/NEventStore/NEventStore - git - license.txt - True - True - latest-recommended - - - TRACE;DEBUG - - - - True - \ - - - True - \ - - - True - \ - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - True - True - Messages.resx - - - - - ResXFileCodeGenerator - Messages.Designer.cs - NEventStore.Serialization.MsgPack - - - diff --git a/src/NEventStore.Serialization.MsgPack/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.MsgPack/Properties/AssemblyInfo.cs deleted file mode 100644 index 4afdf2255..000000000 --- a/src/NEventStore.Serialization.MsgPack/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.MsgPack")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore.Serialization.MsgPack")] -[assembly: AssemblyCopyright("Copyright © 2024")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en-US")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("b0b4110c-9285-4c18-ba2c-e5d4b1731b5b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] \ No newline at end of file diff --git a/src/NEventStore.Serialization.Rijndael.Tests/NEventStore.Serialization.Rijndael.Core.Tests.csproj b/src/NEventStore.Serialization.Rijndael.Tests/NEventStore.Serialization.Rijndael.Core.Tests.csproj deleted file mode 100644 index 0b0a6ebde..000000000 --- a/src/NEventStore.Serialization.Rijndael.Tests/NEventStore.Serialization.Rijndael.Core.Tests.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - true - false - - exe - - NEventStore.Serialization.Rijndael.Tests - NEventStore.Serialization.Rijndael.Tests - true - - - - TRACE;DEBUG;NUNIT - - - NUNIT - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.Rijndael.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.Rijndael.Tests/NUnitTestAdapterTestsDiscovery.cs deleted file mode 100644 index 25dac50c3..000000000 --- a/src/NEventStore.Serialization.Rijndael.Tests/NUnitTestAdapterTestsDiscovery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; - -namespace NEventStore.Serialization.Json.Tests -{ - /// - /// this is needed to allow NUnit test adapter to discover the tests, - /// if we do not have any class explicitly marked with the TestFixture attribute - /// all the assembly will be ignored (even if some class inherit from something which is - /// marked with the attribute in a reference assembly) - /// -#if NUNIT - [TestFixture] - public class NUnitTestAdapterTestsDiscovery - { - } -#endif -} diff --git a/src/NEventStore.Serialization.Rijndael.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.Rijndael.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index d59819ba7..000000000 --- a/src/NEventStore.Serialization.Rijndael.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.Rijndael.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("A4E30DF3-EE23-4C74-B03B-FB438C66B688")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.Rijndael.Tests/RijndaelSerializerTests.cs b/src/NEventStore.Serialization.Rijndael.Tests/RijndaelSerializerTests.cs deleted file mode 100644 index 6fc6c9224..000000000 --- a/src/NEventStore.Serialization.Rijndael.Tests/RijndaelSerializerTests.cs +++ /dev/null @@ -1,127 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using NEventStore.Serialization.Binary; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore.Serialization.Rijndael.Tests -{ -#if MSTEST - [TestClass] -#endif - public class when_round_tripping_a_large_payload_with_rijndael_serializer : SpecificationBase - { - private static readonly byte[] EncryptionKey = - [ - 0x1, 0x2, 0x3, 0x4, - 0x5, 0x6, 0x7, 0x8, - 0x9, 0xa, 0xb, 0xc, - 0xd, 0xe, 0xf, 0x0 - ]; - - private readonly EventMessage _message = - new() - { - Body = new SimpleMessage - { - Id = Guid.NewGuid(), - Created = new DateTime(2026, 4, 17, 0, 0, 0, DateTimeKind.Utc), - Value = new string('x', 128 * 1024), - Count = 42 - } - }; - - private byte[]? _serialized; - private EventMessage? _deserialized; - - protected override void Context() - { - _message.Headers["payload"] = "large"; - } - - protected override void Because() - { -#pragma warning disable CS0618 - var serializer = new RijndaelSerializer(new BinarySerializer(), EncryptionKey); -#pragma warning restore CS0618 - _serialized = serializer.Serialize(_message); - // Force the IV prefix to arrive in tiny chunks so the regression fails if the wrapper - // assumes a single Stream.Read call will always return the full IV before decryption. - using var stream = new ChunkedReadStream(new MemoryStream(_serialized!), 3); - _deserialized = serializer.Deserialize(stream); - } - - [Fact] - public void should_round_trip_the_large_message_body() - { - var payload = _deserialized!.Body.Should().BeOfType().Subject; - payload.Value.Should().Be(((SimpleMessage)_message.Body).Value); - payload.Count.Should().Be(((SimpleMessage)_message.Body).Count); - } - - [Fact] - public void should_round_trip_event_headers_through_the_wrapper() - { - _deserialized!.Headers["payload"].Should().Be("large"); - } - } - - internal sealed class ChunkedReadStream(Stream inner, int maxChunkSize) : Stream - { - public override bool CanRead => inner.CanRead; - public override bool CanSeek => inner.CanSeek; - public override bool CanWrite => false; - public override long Length => inner.Length; - - public override long Position - { - get => inner.Position; - set => inner.Position = value; - } - - public override void Flush() - { - inner.Flush(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return inner.Read(buffer, offset, Math.Min(count, maxChunkSize)); - } - - public override long Seek(long offset, SeekOrigin origin) - { - return inner.Seek(offset, origin); - } - - public override void SetLength(long value) - { - inner.SetLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - inner.Dispose(); - } - - base.Dispose(disposing); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Serialization.Rijndael.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.Rijndael.Tests/SerializerFixture.cs deleted file mode 100644 index 32b43e97d..000000000 --- a/src/NEventStore.Serialization.Rijndael.Tests/SerializerFixture.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ReSharper disable CheckNamespace -using NEventStore.Serialization.Binary; - -namespace NEventStore.Serialization.AcceptanceTests -// ReSharper restore CheckNamespace -{ - public partial class SerializerFixture - { - private static readonly byte[] EncryptionKey = new byte[] - {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0}; - - public SerializerFixture() - { -#if NET8_0_OR_GREATER - // BinarySerializer: added EnableUnsafeBinaryFormatterSerialization in .csproj - // AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); -#endif -#pragma warning disable CS0618 // Type or member is obsolete - _createSerializer = () => new RijndaelSerializer(new BinarySerializer(), EncryptionKey); -#pragma warning restore CS0618 // Type or member is obsolete - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Serialization.SystemTextJson.Tests/NEventStore.Serialization.SystemTextJson.Core.Tests.csproj b/src/NEventStore.Serialization.SystemTextJson.Tests/NEventStore.Serialization.SystemTextJson.Core.Tests.csproj deleted file mode 100644 index 52451ca81..000000000 --- a/src/NEventStore.Serialization.SystemTextJson.Tests/NEventStore.Serialization.SystemTextJson.Core.Tests.csproj +++ /dev/null @@ -1,41 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - false - - exe - - NEventStore.Serialization.SystemTextJson.Tests - NEventStore.Serialization.SystemTextJson.Tests - true - - - - TRACE;DEBUG;NUNIT - - - NUNIT - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/NEventStore.Serialization.SystemTextJson.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Serialization.SystemTextJson.Tests/NUnitTestAdapterTestsDiscovery.cs deleted file mode 100644 index 32572a7c8..000000000 --- a/src/NEventStore.Serialization.SystemTextJson.Tests/NUnitTestAdapterTestsDiscovery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; - -namespace NEventStore.Serialization.SystemTextJson.Tests -{ - /// - /// this is needed to allow NUnit test adapter to discover the tests, - /// if we do not have any class explicitly marked with the TestFixture attribute - /// all the assembly will be ignored (even if some class inherit from something which is - /// marked with the attribute in a reference assembly) - /// -#if NUNIT - [TestFixture] - public class NUnitTestAdapterTestsDiscovery - { - } -#endif -} diff --git a/src/NEventStore.Serialization.SystemTextJson.Tests/NewtonsoftCompatibilityTests.cs b/src/NEventStore.Serialization.SystemTextJson.Tests/NewtonsoftCompatibilityTests.cs deleted file mode 100644 index 258216d7b..000000000 --- a/src/NEventStore.Serialization.SystemTextJson.Tests/NewtonsoftCompatibilityTests.cs +++ /dev/null @@ -1,251 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using NewtonsoftJsonSerializer = NEventStore.Serialization.Json.JsonSerializer; - -namespace NEventStore.Serialization.SystemTextJson.Tests -{ - public class when_deserializing_newtonsoft_event_messages_with_system_text_json : SpecificationBase - { - private readonly List _messages = - [ - new EventMessage { Body = "some value" }, - new EventMessage { Body = 42 }, - new EventMessage { Body = new SimpleMessage().Populate() }, - ]; - - private List? _deserialized; - - protected override void Because() - { - var serialized = new NewtonsoftJsonSerializer(null).Serialize(_messages); - _deserialized = new SystemTextJsonSerializer().Deserialize>(serialized); - } - - [Fact] - public void should_deserialize_complex_event_body_types() - { - _deserialized!.Last().Body.Should().BeOfType(); - } - } - - public class when_deserializing_system_text_json_event_messages_with_newtonsoft : SpecificationBase - { - private readonly List _messages = - [ - new EventMessage { Body = "some value" }, - new EventMessage { Body = 42 }, - new EventMessage { Body = new SimpleMessage().Populate() }, - ]; - - private List? _deserialized; - - protected override void Because() - { - var serialized = new SystemTextJson.SystemTextJsonSerializer().Serialize(_messages); - _deserialized = new NewtonsoftJsonSerializer(null).Deserialize>(serialized); - } - - [Fact] - public void should_deserialize_complex_event_body_types() - { - _deserialized!.Last().Body.Should().BeOfType(); - } - } - - public class when_deserializing_newtonsoft_snapshot_payloads_with_system_text_json : SpecificationBase - { - private readonly Snapshot _snapshot = new Snapshot(Guid.NewGuid().ToString(), 42, new Dictionary> - { - ["values"] = [1, 2, 3], - }); - - private Snapshot? _deserialized; - - protected override void Because() - { - var serialized = new NewtonsoftJsonSerializer(null).Serialize(_snapshot); - _deserialized = new SystemTextJson.SystemTextJsonSerializer().Deserialize(serialized); - } - - [Fact] - public void should_deserialize_payload_type() - { - _deserialized!.Payload.Should().BeOfType(_snapshot.Payload.GetType()); - } - - [Fact] - public void should_deserialize_payload_contents() - { - _deserialized!.Payload.Should().BeEquivalentTo(_snapshot.Payload); - } - } - - public class when_deserializing_newtonsoft_headers_with_system_text_json : SpecificationBase - { - private readonly Dictionary _headers = new() - { - ["HeaderKey"] = "SomeValue", - ["NumericKey"] = 42, - ["ComplexKey"] = new SimpleMessage().Populate(), - }; - - private Dictionary? _deserialized; - - protected override void Because() - { - var serialized = new NewtonsoftJsonSerializer(null).Serialize(_headers); - _deserialized = new SystemTextJson.SystemTextJsonSerializer().Deserialize>(serialized); - } - - [Fact] - public void should_deserialize_complex_header_types() - { - _deserialized!["ComplexKey"].Should().BeOfType(); - } - } - - public class when_deserializing_system_text_json_snapshot_payloads_with_newtonsoft : SpecificationBase - { - private readonly Snapshot _snapshot = new Snapshot(Guid.NewGuid().ToString(), 42, new Dictionary> - { - ["values"] = [1, 2, 3], - }); - - private Snapshot? _deserialized; - - protected override void Because() - { - var serialized = new SystemTextJson.SystemTextJsonSerializer().Serialize(_snapshot); - _deserialized = new NewtonsoftJsonSerializer(null).Deserialize(serialized); - } - - [Fact] - public void should_deserialize_payload_type() - { - _deserialized!.Payload.Should().BeOfType(_snapshot.Payload.GetType()); - } - - [Fact] - public void should_deserialize_payload_contents() - { - _deserialized!.Payload.Should().BeEquivalentTo(_snapshot.Payload); - } - } - - public class when_roundtripping_snapshot_with_polymorphic_object_dictionary_values : SpecificationBase - { - private readonly Snapshot _snapshot = new Snapshot( - Guid.NewGuid().ToString(), - Guid.NewGuid().ToString(), - 1, - new Dictionary - { - ["text"] = "hello", - ["number"] = 42L, - ["complex"] = new SimpleMessage { Value = "test", Count = 7 }, - }); - - private Snapshot? _deserialized; - - protected override void Because() - { - var serializer = new SystemTextJson.SystemTextJsonSerializer(); - var serialized = serializer.Serialize(_snapshot); - _deserialized = serializer.Deserialize(serialized); - } - - [Fact] - public void should_preserve_payload_type() - { - _deserialized!.Payload.Should().BeOfType>(); - } - - [Fact] - public void should_preserve_complex_value_type() - { - var payload = (Dictionary)_deserialized!.Payload!; - payload["complex"].Should().BeOfType(); - } - - [Fact] - public void should_preserve_complex_value_data() - { - var payload = (Dictionary)_deserialized!.Payload!; - payload["complex"].Should().BeEquivalentTo(new SimpleMessage { Value = "test", Count = 7 }); - } - } - - public class when_deserializing_newtonsoft_snapshot_with_polymorphic_object_dictionary_values_using_system_text_json : SpecificationBase - { - private readonly Snapshot _snapshot = new Snapshot( - Guid.NewGuid().ToString(), - Guid.NewGuid().ToString(), - 1, - new Dictionary - { - ["text"] = "hello", - ["complex"] = new SimpleMessage { Value = "compat", Count = 3 }, - }); - - private Snapshot? _deserialized; - - protected override void Because() - { - var serialized = new NewtonsoftJsonSerializer(null).Serialize(_snapshot); - _deserialized = new SystemTextJson.SystemTextJsonSerializer().Deserialize(serialized); - } - - [Fact] - public void should_preserve_complex_value_type() - { - var payload = (Dictionary)_deserialized!.Payload!; - payload["complex"].Should().BeOfType(); - } - - [Fact] - public void should_preserve_complex_value_data() - { - var payload = (Dictionary)_deserialized!.Payload!; - payload["complex"].Should().BeEquivalentTo(new SimpleMessage { Value = "compat", Count = 3 }); - } - } - - public class when_deserializing_nested_generic_type_metadata_with_mismatched_outer_assembly_version : SpecificationBase - { - private readonly Dictionary> _payload = new() - { - ["values"] = [1, 2, 3], - }; - - private Dictionary>? _deserialized; - - protected override void Because() - { - var serializer = new SystemTextJson.SystemTextJsonSerializer(); - var serializedBytes = serializer.Serialize(_payload); - var serialized = System.Text.Encoding.UTF8.GetString(serializedBytes); - var typeName = typeof(Dictionary>).AssemblyQualifiedName!; - serialized = serialized.Replace(typeName, GetTypeNameWithMismatchedOuterAssemblyVersion(typeName)); - _deserialized = serializer.Deserialize>>(System.Text.Encoding.UTF8.GetBytes(serialized)); - } - - [Fact] - public void should_deserialize_nested_generic_dictionary() - { - _deserialized!.Should().BeEquivalentTo(_payload); - } - - private static string GetTypeNameWithMismatchedOuterAssemblyVersion(string typeName) - { - var assemblySeparatorIndex = typeName.LastIndexOf("]], ", StringComparison.Ordinal); - if (assemblySeparatorIndex < 0) - { - throw new InvalidOperationException("Expected a generic assembly-qualified type name."); - } - - var outerAssemblyName = typeof(Dictionary>).Assembly.GetName().Name; - return typeName.Substring(0, assemblySeparatorIndex + 4) + outerAssemblyName + ", Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"; - } - } -} diff --git a/src/NEventStore.Serialization.SystemTextJson.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.SystemTextJson.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 5381c1e9c..000000000 --- a/src/NEventStore.Serialization.SystemTextJson.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.SystemTextJson.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore")] -[assembly: AssemblyCopyright("Copyright © 2026")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("1b309612-b61a-4b02-a7aa-7a62a7e5606d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Serialization.SystemTextJson.Tests/SerializerFixture.cs b/src/NEventStore.Serialization.SystemTextJson.Tests/SerializerFixture.cs deleted file mode 100644 index ab51fe1d2..000000000 --- a/src/NEventStore.Serialization.SystemTextJson.Tests/SerializerFixture.cs +++ /dev/null @@ -1,15 +0,0 @@ -// ReSharper disable CheckNamespace -using NEventStore.Serialization.SystemTextJson; - -namespace NEventStore.Serialization.AcceptanceTests -// ReSharper restore CheckNamespace -{ - public partial class SerializerFixture - { - public SerializerFixture() - { - _createSerializer = () => - new SystemTextJsonSerializer(); - } - } -} diff --git a/src/NEventStore.Serialization.SystemTextJson/Messages.Designer.cs b/src/NEventStore.Serialization.SystemTextJson/Messages.Designer.cs deleted file mode 100644 index 90ff9d2a2..000000000 --- a/src/NEventStore.Serialization.SystemTextJson/Messages.Designer.cs +++ /dev/null @@ -1,81 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NEventStore.Serialization.SystemTextJson { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.SystemTextJson.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - } -} diff --git a/src/NEventStore.Serialization.SystemTextJson/Messages.resx b/src/NEventStore.Serialization.SystemTextJson/Messages.resx deleted file mode 100644 index 971eea1e1..000000000 --- a/src/NEventStore.Serialization.SystemTextJson/Messages.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - - \ No newline at end of file diff --git a/src/NEventStore.Serialization.SystemTextJson/NEventStore.Serialization.SystemTextJson.Core.csproj b/src/NEventStore.Serialization.SystemTextJson/NEventStore.Serialization.SystemTextJson.Core.csproj deleted file mode 100644 index 82e22579a..000000000 --- a/src/NEventStore.Serialization.SystemTextJson/NEventStore.Serialization.SystemTextJson.Core.csproj +++ /dev/null @@ -1,78 +0,0 @@ - - - netstandard2.0;$(ModernTargetFrameworks) - false - NEventStore.Serialization.SystemTextJson - NEventStore.Serialization.SystemTextJson - - - NEventStore.Serialization.SystemTextJson - NEventStore System.Text.Json Serializers - NEventStore Dev Team - http://neventstore.org - false - This package contains Json serializers for NEventStore library. These serializers were removed from the core package to limit dependencies from external libraries. - events, event sourcing, cqrs, storage, persistence, database - - True - true - true - snupkg - true - true - True - True - NEventStore Dev Team - icon.png - README.md - https://github.com/NEventStore/NEventStore - git - license.txt - True - True - latest-recommended - - - TRACE;DEBUG - - - - True - \ - - - True - \ - - - True - \ - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - True - True - Messages.resx - - - - - ResXFileCodeGenerator - Messages.Designer.cs - NEventStore.Serialization.SystemTextJson - - - diff --git a/src/NEventStore.Serialization.SystemTextJson/Properties/AssemblyInfo.cs b/src/NEventStore.Serialization.SystemTextJson/Properties/AssemblyInfo.cs deleted file mode 100644 index 4dc2caad1..000000000 --- a/src/NEventStore.Serialization.SystemTextJson/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Serialization.SystemTextJson")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore.Serialization.SystemTextJson")] -[assembly: AssemblyCopyright("Copyright © 2026")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en-US")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("01d53993-84d8-4b6a-942b-69d72897de00")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: AssemblyInformationalVersion("0.0.0.0")] diff --git a/src/NEventStore.Serialization.SystemTextJson/README.md b/src/NEventStore.Serialization.SystemTextJson/README.md deleted file mode 100644 index 2972a231e..000000000 --- a/src/NEventStore.Serialization.SystemTextJson/README.md +++ /dev/null @@ -1,171 +0,0 @@ -# NEventStore.Serialization.SystemTextJson - -`NEventStore.Serialization.SystemTextJson` is the System.Text.Json serializer for NEventStore. It is implemented as a drop-in JSON replacement for `NEventStore.Serialization.Json`, including Newtonsoft-compatible type metadata for polymorphic NEventStore payloads. - -## Wireup - -```csharp -var store = Wireup.Init() - .UsingInMemoryPersistence() - .UsingJsonSerialization() - .Build(); -``` - -Custom System.Text.Json options can be supplied, and root known types can be overridden: - -```csharp -var serializer = new SystemTextJsonSerializer( - new JsonSerializerOptions { WriteIndented = true }, - typeof(List), - typeof(Dictionary)); -``` - -The default known types are: - -- `List` -- `Dictionary` - -Passing `null` or an empty known-type array keeps those defaults. Passing a non-empty array replaces them. - -## Implementation Model - -System.Text.Json does not support Newtonsoft.Json `TypeNameHandling`. This serializer implements the NEventStore-compatible subset explicitly: - -- Known root types are serialized with normal System.Text.Json object handling and no root `$type`. -- Other root types are wrapped with a `$type` property before their regular JSON properties. -- Polymorphic `object` values are handled by a custom `JsonConverter`. -- `Snapshot` is handled by a custom converter because its key properties have private setters and its payload is declared as `object`. -- `JsonObjectCreationHandling.Populate` is enabled so get-only collection properties, such as `SimpleMessage.Contents`, round-trip like Newtonsoft. - -The serializer copies user-provided `JsonSerializerOptions` before adding its compatibility converters. It always sets `DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull` to match the Newtonsoft serializer's null omission behavior. - -## Compatibility Layer - -The compatibility layer writes and reads Newtonsoft-style metadata: - -- `$type` stores an assembly-qualified CLR type name. -- `$values` stores array or collection contents when the typed value is represented as a metadata object. -- Primitive `object` values are written as JSON primitives without `$type`, matching Newtonsoft behavior for strings and numbers. -- Complex `object` values are written with `$type` metadata. -- Typed dictionaries are normalized on read so Newtonsoft payloads such as `{ "$type": "...List...", "$values": [...] }` can be deserialized by System.Text.Json into the declared dictionary value type. - -The type resolver first uses `Type.GetType`. If the assembly-qualified name cannot be resolved directly, it falls back to loaded assemblies by full type name. That keeps deserialization tolerant of normal assembly-load ordering. - -The serializers are wire-compatible; they are not required to produce byte-for-byte identical JSON when static property types already provide enough type information. For example, System.Text.Json can write a typed dictionary value list as a normal JSON array while still reading Newtonsoft's `$type` plus `$values` representation for the same list. - -## Serialized Data Examples - -Assembly versions are shortened in these examples. Real payloads contain full assembly-qualified names. - -### Event Messages - -Source object: - -```csharp -var messages = new List -{ - new EventMessage { Body = "some value" }, - new EventMessage { Body = 42 }, - new EventMessage { Body = new SimpleMessage { Count = 1234, Value = "Hello" } } -}; -``` - -JSON shape: - -```json -[ - { - "Headers": {}, - "Body": "some value" - }, - { - "Headers": {}, - "Body": 42 - }, - { - "Headers": {}, - "Body": { - "$type": "NEventStore.Persistence.AcceptanceTests.SimpleMessage, NEventStore.Persistence.AcceptanceTests", - "Id": "00000000-0000-0000-0000-000000000000", - "Created": "0001-01-01T00:00:00", - "Value": "Hello", - "Count": 1234, - "Contents": [] - } - } -] -``` - -`List` is a known root type, so the root array has no `$type`. The complex event body is declared as `object`, so the compatibility converter writes `$type` metadata. - -### Commit Headers - -Source object: - -```csharp -var headers = new Dictionary -{ - ["HeaderKey"] = "SomeValue", - ["NumericKey"] = 42, - ["ComplexKey"] = new SimpleMessage { Count = 1234 } -}; -``` - -JSON shape: - -```json -{ - "HeaderKey": "SomeValue", - "NumericKey": 42, - "ComplexKey": { - "$type": "NEventStore.Persistence.AcceptanceTests.SimpleMessage, NEventStore.Persistence.AcceptanceTests", - "Id": "00000000-0000-0000-0000-000000000000", - "Created": "0001-01-01T00:00:00", - "Count": 1234, - "Contents": [] - } -} -``` - -`Dictionary` is a known root type. The complex value is typed; primitive values remain primitive. - -### Snapshot Payloads - -Source object: - -```csharp -var snapshot = new Snapshot("stream-1", 42, new Dictionary> -{ - ["values"] = [1, 2, 3] -}); -``` - -JSON shape: - -```json -{ - "$type": "NEventStore.Snapshot, NEventStore", - "BucketId": "default", - "StreamId": "stream-1", - "StreamRevision": 42, - "Payload": { - "$type": "System.Collections.Generic.Dictionary`2[[System.String,...],[System.Collections.Generic.List`1[[System.Int32,...]],...]], ...", - "values": { - "$type": "System.Collections.Generic.List`1[[System.Int32,...]], ...", - "$values": [1, 2, 3] - } - } -} -``` - -The `Snapshot` converter preserves private-set properties and passes `Payload` through the metadata compatibility layer. The shape is intentionally readable by Newtonsoft.Json. When the declared payload type already contains the nested list type, System.Text.Json may write the nested `values` member as a plain array; it still reads the Newtonsoft `$type` plus `$values` shape shown above. - -## Swap Verification - -The test suite verifies: - -- System.Text.Json round-trips through the shared serialization acceptance tests. -- Newtonsoft output can be read by System.Text.Json for event messages, headers, and snapshot payloads. -- System.Text.Json output can be read by Newtonsoft for event messages, headers, and snapshot payloads. - -The mirrored Newtonsoft tests verify the opposite package direction from that project as well. diff --git a/src/NEventStore.Serialization.SystemTextJson/SystemTextJsonSerializationWireupExtension.cs b/src/NEventStore.Serialization.SystemTextJson/SystemTextJsonSerializationWireupExtension.cs deleted file mode 100644 index 507340f1b..000000000 --- a/src/NEventStore.Serialization.SystemTextJson/SystemTextJsonSerializationWireupExtension.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text.Json; - -namespace NEventStore.Serialization.SystemTextJson -{ - /// - /// System.Text.Json serialization wire-up extensions. - /// - public static class SystemTextJsonSerializationWireupExtension - { - /// - /// Specify we want to use Json serialization using System.Text.Json - /// - /// The persistence wire-up. - /// - /// Allows to customize some Serializer options, however some of them will - /// be under control of this specific implementation and will be overwritten no matter - /// what the user specifies: - /// - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - /// - PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate - /// - SnapshotJsonConverter and ObjectJsonConverter will be added to the Converters collection - /// - /// - /// Every Type specified here will be serialized without root type metadata. - /// Every other root type and polymorphic object value will be serialized with Newtonsoft-compatible - /// $type metadata. - /// - public static SerializationWireup UsingJsonSerialization( - this PersistenceWireup wireup, - JsonSerializerOptions? jsonSerializerOptions = null, - params Type[]? knownTypes) - { - return wireup.UsingCustomSerialization(new SystemTextJsonSerializer(jsonSerializerOptions, knownTypes)); - } - } -} diff --git a/src/NEventStore.Serialization.SystemTextJson/SystemTextJsonSerializer.cs b/src/NEventStore.Serialization.SystemTextJson/SystemTextJsonSerializer.cs deleted file mode 100644 index bef77e751..000000000 --- a/src/NEventStore.Serialization.SystemTextJson/SystemTextJsonSerializer.cs +++ /dev/null @@ -1,515 +0,0 @@ -using System.Collections; -using System.Collections.Concurrent; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore.Serialization.SystemTextJson -{ - /// - /// The System.Text.Json JSON serializer. - /// - public class SystemTextJsonSerializer : ISerialize - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(SystemTextJsonSerializer)); - private static readonly Type[] DefaultKnownTypes = [typeof(List), typeof(Dictionary)]; - - private readonly HashSet _knownTypes; - private readonly JsonSerializerOptions _jsonSerializerOptions; - - /// - /// NEventStore Json Serialization using System.Text.Json - /// - /// - /// Allows to customize some Serializer options, however some of them will - /// be under control of this specific implementation and will be overwritten no matter - /// what the user specifies: - /// - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - /// - PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate - /// - SnapshotJsonConverter and ObjectJsonConverter will be added to the Converters collection - /// - /// - /// Every Type specified here will be serialized without root type metadata. - /// Every other root type and polymorphic object value will be serialized with Newtonsoft-compatible - /// $type metadata. - /// - public SystemTextJsonSerializer(JsonSerializerOptions? jsonSerializerOptions = null, params Type[]? knownTypes) - { - if (knownTypes?.Length == 0) - { - knownTypes = null; - } - - _knownTypes = new HashSet(knownTypes ?? DefaultKnownTypes); - _jsonSerializerOptions = jsonSerializerOptions is null - ? new JsonSerializerOptions() - : new JsonSerializerOptions(jsonSerializerOptions); - _jsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; - _jsonSerializerOptions.PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate; - _jsonSerializerOptions.Converters.Add(new SnapshotJsonConverter()); - _jsonSerializerOptions.Converters.Add(new ObjectJsonConverter(_jsonSerializerOptions)); - - if (Logger.IsEnabled(LogLevel.Debug)) - { - foreach (var type in _knownTypes) - { - Logger.LogDebug("Registering known type '{Type}'.", type); - } - } - } - - /// - public virtual void Serialize(Stream output, T graph) where T : notnull - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.SerializingGraph, typeof(T)); - } - using var jsonWriter = new Utf8JsonWriter(output); - Serialize(jsonWriter, graph); - jsonWriter.Flush(); - } - - /// - public virtual T? Deserialize(Stream input) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.DeserializingStream, typeof(T)); - } - - Type type = typeof(T); - if (_knownTypes.Contains(type)) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace("Using untyped serializer for type '{Type}'.", type); - } - - return JsonSerializer.Deserialize(input, _jsonSerializerOptions); - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace("Using typed serializer for type '{Type}'.", type); - } - - using var document = JsonDocument.Parse(input); - return (T?)TypeMetadata.Read(document.RootElement, type, _jsonSerializerOptions); - } - - /// - /// Serialize an object to a Utf8JsonWriter. - /// - protected virtual void Serialize(Utf8JsonWriter writer, object graph) - { - var type = graph.GetType(); - if (_knownTypes.Contains(type)) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace("Using untyped serializer for type '{Type}'.", type); - } - - JsonSerializer.Serialize(writer, graph, type, _jsonSerializerOptions); - return; - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace("Using typed serializer for type '{Type}'.", type); - } - - TypeMetadata.Write(writer, graph, type, _jsonSerializerOptions); - } - - private sealed class ObjectJsonConverter : JsonConverter - { - private readonly JsonSerializerOptions _options; - - public ObjectJsonConverter(JsonSerializerOptions options) - { - _options = options; - } - - public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var document = JsonDocument.ParseValue(ref reader); - return TypeMetadata.ReadObject(document.RootElement, _options); - } - - public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) - { - TypeMetadata.WriteObject(writer, value, _options); - } - } - - private sealed class SnapshotJsonConverter : JsonConverter - { - public override Snapshot? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var document = JsonDocument.ParseValue(ref reader); - var root = document.RootElement; - var bucketId = root.GetProperty(nameof(Snapshot.BucketId)).GetString(); - var streamId = root.GetProperty(nameof(Snapshot.StreamId)).GetString(); - var streamRevision = root.GetProperty(nameof(Snapshot.StreamRevision)).GetInt32(); - var payload = root.TryGetProperty(nameof(Snapshot.Payload), out var payloadProperty) - ? TypeMetadata.ReadObject(payloadProperty, options) - : null; - - return new Snapshot(bucketId!, streamId!, streamRevision, payload!); - } - - public override void Write(Utf8JsonWriter writer, Snapshot value, JsonSerializerOptions options) - { - writer.WriteStartObject(); - writer.WriteString(nameof(Snapshot.BucketId), value.BucketId); - writer.WriteString(nameof(Snapshot.StreamId), value.StreamId); - writer.WriteNumber(nameof(Snapshot.StreamRevision), value.StreamRevision); - writer.WritePropertyName(nameof(Snapshot.Payload)); - TypeMetadata.WriteObject(writer, value.Payload, options); - writer.WriteEndObject(); - } - } - - /// - /// Handles reading and writing the Newtonsoft.Json-compatible $type / $values wire format, - /// so that payloads written by either serializer can be read by the other. - /// - /// - /// Wire format - /// - /// Object types: { "$type": "<AssemblyQualifiedName>", "Prop1": ..., "Prop2": ... } - /// Collection types: { "$type": "<AssemblyQualifiedName>", "$values": [ ... ] } - /// Simple/primitive values are written inline without any wrapper. - /// - /// Write path - /// - /// — entry point for ? slots - /// (e.g. EventMessage.Body, Snapshot.Payload, dictionary values). - /// Writes null, delegates to the STJ default serializer for simple types, or calls for everything else. - /// — serialises the value using STJ, then wraps the resulting element in the - /// { $type, ... } envelope. Object elements have their properties inlined; - /// array/primitive elements are placed under the $values key. - /// - /// Read path - /// - /// — entry point for untyped ? slots. - /// Dispatches on the JSON value kind: primitives are returned directly, arrays become - /// of ?, objects are forwarded to with - /// as the fallback type. - /// — looks for a $type property to determine the concrete type. - /// Without $type the element is deserialised as-is using the fallback type. With $type: - /// if $values is present the payload is a collection and its array is deserialised directly; - /// otherwise $type is stripped from the JSON, nested collection wrappers are optionally normalised - /// (see ), and STJ deserialises the remainder. - /// - /// Normalisation - /// STJ cannot process Newtonsoft-style { "$type": ..., "$values": [...] } wrappers that appear as - /// nested property values (e.g. a property serialised with TypeNameHandling.All). - /// recursively walks a tree and replaces every - /// { $type, $values } pair with the unwrapped $values content. It does not strip - /// $type from plain object nodes — those still carry type information that ObjectJsonConverter - /// needs to reconstruct polymorphic values. Normalisation is only triggered for - /// types (see ). - /// Caches - /// TypeNames, ResolvedTypes, SimpleTypes, and DictionaryTypes are - /// process-wide caches to avoid repeated reflection on hot paths. - /// - private static class TypeMetadata - { - private const string TypePropertyName = "$type"; - private const string ValuesPropertyName = "$values"; - private static readonly ConcurrentDictionary TypeNames = new(); - private static readonly ConcurrentDictionary ResolvedTypes = new(); - private static readonly ConcurrentDictionary SimpleTypes = new(); - private static readonly ConcurrentDictionary DictionaryTypes = new(); - - /// - /// Entry point for writing any ? slot whose declared type is . - /// Simple/primitive values bypass the $type wrapper; complex values go through . - /// - public static void WriteObject(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) - { - if (value is null) - { - writer.WriteNullValue(); - return; - } - - var type = value.GetType(); - if (IsSimple(type)) - { - JsonSerializer.Serialize(writer, value, type, options); - return; - } - - Write(writer, value, type, options); - } - - /// - /// Wraps a complex value in a { "$type": "...", <properties> } envelope. - /// - /// - /// STJ first serialises the value normally; the resulting element is then re-emitted under - /// the $type wrapper. Collections produce a $values array instead of inlined properties. - /// - public static void Write(Utf8JsonWriter writer, object value, Type type, JsonSerializerOptions options) - { - var element = JsonSerializer.SerializeToElement(value, type, options); - - writer.WriteStartObject(); - writer.WriteString(TypePropertyName, GetTypeName(type)); - if (element.ValueKind == JsonValueKind.Object) - { - foreach (var property in element.EnumerateObject()) - { - property.WriteTo(writer); - } - } - else - { - // Non-object (array, primitive): store under $values so the read path - // can distinguish a collection wrapper from a plain object wrapper. - writer.WritePropertyName(ValuesPropertyName); - element.WriteTo(writer); - } - - writer.WriteEndObject(); - } - - /// - /// Entry point for reading a JSON element whose target type is . - /// - /// - /// Primitive JSON kinds are mapped to their natural CLR types; arrays become - /// of ?; objects are forwarded to - /// with a fallback. - /// - public static object? ReadObject(JsonElement element, JsonSerializerOptions options) - { - return element.ValueKind switch - { - JsonValueKind.Null => null, - JsonValueKind.String => element.GetString(), - JsonValueKind.True => true, - JsonValueKind.False => false, - JsonValueKind.Number => ReadNumber(element), - JsonValueKind.Array => JsonSerializer.Deserialize>(element.GetRawText(), options), - JsonValueKind.Object => Read(element, typeof(Dictionary), options), - _ => null, - }; - } - - /// - /// Reads a JSON element into a typed CLR object, honouring $type metadata. - /// - /// - /// Without $type the is used directly. - /// With $type: - /// - /// $values present — collection wrapper; deserialise the array as the resolved type. - /// No $values — plain object wrapper; remove $type, normalise nested collection - /// wrappers for types, then deserialise the remainder. - /// - /// - public static object? Read(JsonElement element, Type fallbackType, JsonSerializerOptions options) - { - if (element.ValueKind != JsonValueKind.Object || - !element.TryGetProperty(TypePropertyName, out var typeProperty)) - { - return JsonSerializer.Deserialize(element.GetRawText(), fallbackType, options); - } - - var type = ResolveType(typeProperty.GetString()!); - if (element.TryGetProperty(ValuesPropertyName, out var valuesProperty)) - { - // Collection wrapper: the actual payload lives in $values. - return JsonSerializer.Deserialize(valuesProperty.GetRawText(), type, options); - } - - // Plain object wrapper: strip the outer $type, then hand the remaining JSON to STJ. - var node = JsonNode.Parse(element.GetRawText())!.AsObject(); - node.Remove(TypePropertyName); - var json = ShouldNormalizeTypeMetadata(type) - ? NormalizeTypeMetadata(node)!.ToJsonString() - : node.ToJsonString(); - return JsonSerializer.Deserialize(json, type, options); - } - - /// - /// Recursively unwraps Newtonsoft-style { "$type": ..., "$values": [...] } collection - /// wrappers that may appear as property values inside an payload. - /// - /// - /// Only { $type + $values } pairs are collapsed — plain { $type, <properties> } - /// objects are left intact so that ObjectJsonConverter can still resolve their concrete type. - /// This preserves polymorphic object-valued dictionary entries while still allowing STJ to - /// deserialise strongly-typed collection properties (e.g. ) that - /// Newtonsoft wrapped with TypeNameHandling.All. - /// - private static JsonNode? NormalizeTypeMetadata(JsonNode? node) - { - if (node is JsonObject jsonObject) - { - if (jsonObject.TryGetPropertyValue(TypePropertyName, out _) && - jsonObject.TryGetPropertyValue(ValuesPropertyName, out var values)) - { - // Collection wrapper: replace the whole node with its unwrapped content. - return NormalizeTypeMetadata(values?.DeepClone()); - } - - // Plain object: recurse into each property value. - foreach (var property in jsonObject.ToArray()) - { - jsonObject[property.Key] = NormalizeTypeMetadata(property.Value?.DeepClone()); - } - } - else if (node is JsonArray jsonArray) - { - for (var i = 0; i < jsonArray.Count; i++) - { - jsonArray[i] = NormalizeTypeMetadata(jsonArray[i]?.DeepClone()); - } - } - - return node; - } - - /// - /// Returns the numeric value of a JSON number element, preferring over - /// so that integer values round-trip without precision loss. - /// - private static object ReadNumber(JsonElement element) - { - if (element.TryGetInt64(out var integer)) - { - return integer; - } - - return element.GetDouble(); - } - - /// - /// Returns for types that STJ serialises as a plain JSON value, - /// meaning no $type wrapper is needed. - /// - private static bool IsSimple(Type type) - { - return SimpleTypes.GetOrAdd(type, static candidate => - { - candidate = Nullable.GetUnderlyingType(candidate) ?? candidate; - return candidate.IsPrimitive || - candidate.IsEnum || - candidate == typeof(string) || - candidate == typeof(decimal) || - candidate == typeof(Guid) || - candidate == typeof(DateTime) || - candidate == typeof(DateTimeOffset) || - candidate == typeof(TimeSpan); - }); - } - - /// - /// Returns when implements — - /// the only case where needs to run. - /// - private static bool ShouldNormalizeTypeMetadata(Type type) - { - return DictionaryTypes.GetOrAdd(type, static candidate => typeof(IDictionary).IsAssignableFrom(candidate)); - } - - /// - /// Returns the assembly-qualified type name used as the $type value on the wire. - /// - private static string GetTypeName(Type type) - { - return TypeNames.GetOrAdd(type, static candidate => candidate.AssemblyQualifiedName ?? candidate.FullName ?? candidate.Name); - } - - /// - /// Resolves a $type string back to a CLR . - /// - /// - /// Tries - /// first with an assembly resolver that ignores version and public-key token differences. - /// If that fails, it falls back to scanning loaded assemblies using a generic-aware parser that - /// strips only the outer assembly qualification from the $type value. - /// - private static Type ResolveType(string typeName) - { - if (ResolvedTypes.TryGetValue(typeName, out var cachedType)) - { - return cachedType; - } - - var type = Type.GetType(typeName, ResolveAssembly, null, throwOnError: false); - if (type is not null) - { - return ResolvedTypes.GetOrAdd(typeName, type); - } - - var fullName = GetFullName(typeName); - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - type = assembly.GetType(fullName, throwOnError: false); - if (type is not null) - { - return ResolvedTypes.GetOrAdd(typeName, type); - } - } - - throw new JsonException($"Could not resolve serialized type '{typeName}'."); - } - - /// - /// Resolves assemblies by simple name while ignoring version and public-key token differences. - /// - private static Assembly? ResolveAssembly(AssemblyName assemblyName) - { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - var currentAssemblyName = assembly.GetName(); - if (string.Equals(currentAssemblyName.Name, assemblyName.Name, StringComparison.Ordinal)) - { - return assembly; - } - } - - return null; - } - - /// - /// Strips only the outer assembly token from an assembly-qualified type name. - /// - private static string GetFullName(string typeName) - { - var depth = 0; - for (var index = 0; index < typeName.Length; index++) - { - var character = typeName[index]; - if (character == '[') - { - depth++; - } - else if (character == ']') - { - if (depth > 0) - { - depth--; - } - } - else if (character == ',' && depth == 0) - { - return typeName.Substring(0, index).Trim(); - } - } - - return typeName.Trim(); - } - } - } -} diff --git a/src/NEventStore.Tests/Client/AsyncPollingClientTests.cs b/src/NEventStore.Tests/Client/AsyncPollingClientTests.cs deleted file mode 100644 index c2da9e462..000000000 --- a/src/NEventStore.Tests/Client/AsyncPollingClientTests.cs +++ /dev/null @@ -1,398 +0,0 @@ -#pragma warning disable IDE1006 // Naming Styles - -using System.Diagnostics; -using FakeItEasy; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.PollingClient.Async -{ -#if MSTEST - [TestClass] -#endif - public class Creating_AsyncPollingClient_Tests - { - [Fact] - public void When_persist_streams_is_null_then_should_throw() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Catch.Exception(() => new AsyncPollingClient(null, new CommitStreamObserver()).Should().BeOfType()); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void When_interval_less_than_zero_then_should_throw() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Catch.Exception(() => new AsyncPollingClient(A.Fake(), null)).Should().BeOfType(); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - } - -#if MSTEST - [TestClass] -#endif - public class base_handling_committed_events : using_AsyncPollingClient - { - private readonly List commits = []; - - protected override void Context() - { - Observer = new LambdaAsyncObserver((c, _) => { commits.Add(c); return Task.FromResult(true); }); - base.Context(); - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.Start(0); - } - - [Fact] - public void commits_are_correctly_dispatched() - { - WaitForCondition(() => commits.Count >= 1); - commits.Count.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class base_handling_committed_events_and_new_events : using_AsyncPollingClient - { - private readonly List commits = []; - - protected override void Context() - { - Observer = new LambdaAsyncObserver((c, _) => { commits.Add(c); return Task.FromResult(true); }); - base.Context(); - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.Start(0); - for (int i = 0; i < 15; i++) - { - StoreEvents.Advanced.CommitSingle(); - } - } - - [Fact] - public void commits_are_correctly_dispatched() - { - WaitForCondition(() => commits.Count >= 16); - commits.Count.Should().Be(16); - } - } - -#if MSTEST - [TestClass] -#endif - public class verify_stopping_commit_polling_client : using_AsyncPollingClient - { - private readonly List commits = []; - - protected override void Context() - { - Observer = new LambdaAsyncObserver((c, _) => { commits.Add(c); return Task.FromResult(false); }); - base.Context(); - StoreEvents.Advanced.CommitSingle(); - StoreEvents.Advanced.CommitSingle(); - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.Start(0); - } - - [Fact] - public void commits_are_correctly_dispatched() - { - WaitForCondition(() => commits.Count >= 2, timeoutInSeconds: 1); - commits.Count.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class verify_manual_polling : using_AsyncPollingClient - { - private readonly List commits = []; - - protected override void Context() - { - Observer = new LambdaAsyncObserver((c, _) => { commits.Add(c); return Task.FromResult(true); }); - base.Context(); - StoreEvents.Advanced.CommitSingle(); - StoreEvents.Advanced.CommitSingle(); - } - - protected override Task BecauseAsync() - { - Sut.ConfigurePollingClient(); - return Sut.PollAsync(CancellationToken.None); - } - - [Fact] - public void commits_are_retried_then_move_next() - { - WaitForCondition(() => commits.Count >= 2, timeoutInSeconds: 3); - commits.Count.Should().Be(2); - commits - .Select(c => c.CheckpointToken) - .SequenceEqual([1L, 2L]) - .Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_polling_finds_commits_and_more_data_is_immediately_available : using_AsyncPollingClient_with_fake_persistence - { - private readonly List _commits = []; - private readonly List _pollStartTimesInMilliseconds = []; - private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); - - protected override void Context() - { - Observer = new LambdaAsyncObserver((commit, _) => - { - _commits.Add(commit); - return Task.FromResult(true); - }); - - base.Context(); - - A.CallTo(() => Persistence.GetFromAsync(A._, A>._, A._)) - .ReturnsLazily((long checkpointToken, IAsyncObserver observer, CancellationToken cancellationToken) => - { - lock (_pollStartTimesInMilliseconds) - { - _pollStartTimesInMilliseconds.Add(_stopwatch.ElapsedMilliseconds); - } - - return checkpointToken switch - { - 0 => DeliverCommitAsync(observer, 1, cancellationToken), - 1 => DeliverCommitAsync(observer, 2, cancellationToken), - _ => CompleteAsync(observer, cancellationToken) - }; - }); - } - - protected override void Because() - { - Sut.Start(0); - } - - [Fact] - public void should_repoll_without_waiting_for_the_idle_interval_after_progress() - { - WaitForCondition(() => _commits.Count >= 2 && _pollStartTimesInMilliseconds.Count >= 2, timeoutInSeconds: 1); - - _commits.Select(c => c.CheckpointToken).Should().Equal([1L, 2L]); - _pollStartTimesInMilliseconds[1].Should().BeLessThan(PollingInterval); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_stopping_during_a_blocking_poll : using_AsyncPollingClient_with_fake_persistence - { - private readonly TaskCompletionSource _pollStarted = new(TaskCreationOptions.RunContinuationsAsynchronously); - private TimeSpan _stopDuration; - - protected override void Context() - { - base.Context(); - - A.CallTo(() => Persistence.GetFromAsync(A._, A>._, A._)) - .ReturnsLazily(async (long _, IAsyncObserver _, CancellationToken cancellationToken) => - { - _pollStarted.TrySetResult(true); - await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(false); - }); - } - - protected override async Task BecauseAsync() - { - Sut.Start(0); - await _pollStarted.Task.ConfigureAwait(false); - - var stopwatch = Stopwatch.StartNew(); - await Sut.StopAsync().ConfigureAwait(false); - stopwatch.Stop(); - _stopDuration = stopwatch.Elapsed; - } - - [Fact] - public void should_wait_for_the_polling_worker_to_finish_observing_cancellation() - { - _stopDuration.Should().BeLessThan(TimeSpan.FromSeconds(1)); - } - - [Fact] - public void should_not_start_another_poll_after_stop_completes() - { - A.CallTo(() => Persistence.GetFromAsync(A._, A>._, A._)) - .MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_disposing_during_a_blocking_poll : using_AsyncPollingClient_with_fake_persistence - { - private readonly TaskCompletionSource _pollStarted = new(TaskCreationOptions.RunContinuationsAsynchronously); - private TimeSpan _disposeDuration; - - protected override void Context() - { - base.Context(); - - A.CallTo(() => Persistence.GetFromAsync(A._, A>._, A._)) - .ReturnsLazily(async (long _, IAsyncObserver _, CancellationToken cancellationToken) => - { - _pollStarted.TrySetResult(true); - await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(false); - }); - } - - protected override async Task BecauseAsync() - { - Sut.Start(0); - await _pollStarted.Task.ConfigureAwait(false); - - var stopwatch = Stopwatch.StartNew(); - await Task.Run(() => Sut.Dispose()).ConfigureAwait(false); - stopwatch.Stop(); - _disposeDuration = stopwatch.Elapsed; - } - - [Fact] - public void should_wait_for_the_polling_worker_to_finish_before_returning() - { - _disposeDuration.Should().BeLessThan(TimeSpan.FromSeconds(1)); - } - } - - public abstract class using_AsyncPollingClient : SpecificationBase - { - protected const int PollingInterval = 100; - protected AsyncPollingClient? sut; - private IStoreEvents? _storeEvents; - - protected AsyncPollingClient Sut - { - get { return sut!; } - } - - protected IStoreEvents StoreEvents - { - get { return _storeEvents!; } - } - - protected IAsyncObserver Observer { get; set; } = new CommitStreamObserver(); - - protected override void Context() - { - _storeEvents = Wireup.Init().UsingInMemoryPersistence().Build(); - sut = new AsyncPollingClient(_storeEvents.Advanced, Observer, PollingInterval); - } - - protected override void Cleanup() - { - _storeEvents?.Dispose(); - Sut.Dispose(); - } - - protected void WaitForCondition(Func predicate, Int32 timeoutInSeconds = 4) - { - DateTime startTest = DateTime.Now; - while (!predicate() && DateTime.Now.Subtract(startTest).TotalSeconds < timeoutInSeconds) - { - Thread.Sleep(100); - } - } - } - - public abstract class using_AsyncPollingClient_with_fake_persistence : SpecificationBase - { - protected const int PollingInterval = 500; - protected AsyncPollingClient? sut; - - protected AsyncPollingClient Sut - { - get { return sut!; } - } - - protected IPersistStreams Persistence { get; private set; } = null!; - - protected IAsyncObserver Observer { get; set; } = new CommitStreamObserver(); - - protected override void Context() - { - Persistence = A.Fake(); - sut = new AsyncPollingClient(Persistence, Observer, PollingInterval); - } - - protected override void Cleanup() - { - sut?.Dispose(); - } - - protected void WaitForCondition(Func predicate, Int32 timeoutInSeconds = 4) - { - DateTime startTest = DateTime.Now; - while (!predicate() && DateTime.Now.Subtract(startTest).TotalSeconds < timeoutInSeconds) - { - Thread.Sleep(25); - } - } - - protected static async Task DeliverCommitAsync(IAsyncObserver observer, long checkpointToken, CancellationToken cancellationToken) - { - await observer.OnNextAsync(BuildCommit(checkpointToken), cancellationToken).ConfigureAwait(false); - await observer.OnCompletedAsync(cancellationToken).ConfigureAwait(false); - } - - protected static Task CompleteAsync(IAsyncObserver observer, CancellationToken cancellationToken) - { - return observer.OnCompletedAsync(cancellationToken); - } - - protected static ICommit BuildCommit(long checkpointToken) - { - return new Commit( - Bucket.Default, - "polling-stream", - (int)checkpointToken, - Guid.NewGuid(), - (int)checkpointToken, - DateTime.UtcNow, - checkpointToken, - null, - [new EventMessage { Body = $"event-{checkpointToken}" }]); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/Client/CommitSequencerTests.cs b/src/NEventStore.Tests/Client/CommitSequencerTests.cs deleted file mode 100644 index 78d7dd358..000000000 --- a/src/NEventStore.Tests/Client/CommitSequencerTests.cs +++ /dev/null @@ -1,175 +0,0 @@ -#pragma warning disable IDE1006 // Naming Styles - -using System.Collections.Concurrent; -using NEventStore.Helpers; -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -using NUnit.Framework; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.PollingClient -{ -#if MSTEST - [TestClass] -#endif -#if NUNIT - [TestFixture] -#endif - public class CommitSequencerTests - { - private int _outOfSequenceTimeoutInMilliseconds; - - private CommitSequencer InitCommitSequencer(Func? callBack = null) - { - callBack ??= _ => PollingClient2.HandlingResult.MoveToNext; - _outOfSequenceTimeoutInMilliseconds = 2000; - return new CommitSequencer(c => callBack(c), 0, _outOfSequenceTimeoutInMilliseconds); - } - - [Fact] - public void verify_check_sequential_missing_commit() - { - var sut = InitCommitSequencer(); - - var result = sut.Handle(new TestICommit() { CheckpointToken = 1L }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - result = sut.Handle(new TestICommit() { CheckpointToken = 3L }); - result.Should().Be(PollingClient2.HandlingResult.Retry); - } - - [Fact] - public void verify_timeout_on_missing_commit_not_elapsed() - { - var sut = InitCommitSequencer(); - - DateTime start = DateTime.Now; - var result = sut.Handle(new TestICommit() { CheckpointToken = 1L }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - using (DateTimeService.Override(start)) - { - result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); - result.Should().Be(PollingClient2.HandlingResult.Retry); - } - using (DateTimeService.Override(start.AddMilliseconds(_outOfSequenceTimeoutInMilliseconds - 100))) - { - result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); - result.Should().Be(PollingClient2.HandlingResult.Retry); - } - } - - [Fact] - public void verify_idempotence_on_read_same_commit() - { - Int32 callBackCount = 0; - var sut = InitCommitSequencer(_ => - { - callBackCount++; - return PollingClient2.HandlingResult.MoveToNext; - }); - - var result = sut.Handle(new TestICommit() { CheckpointToken = 1 }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - callBackCount.Should().Be(1); - result = sut.Handle(new TestICommit() { CheckpointToken = 1 }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - callBackCount.Should().Be(1); - } - - [Fact] - public void verify_timeout_on_missing_commit_then_next_commit() - { - var sut = InitCommitSequencer(); - - DateTime start = DateTime.Now; - var result = sut.Handle(new TestICommit() { CheckpointToken = 1 }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - using (DateTimeService.Override(start)) - { - result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); - result.Should().Be(PollingClient2.HandlingResult.Retry); - } - using (DateTimeService.Override(start.AddMilliseconds(_outOfSequenceTimeoutInMilliseconds - 100))) - { - result = sut.Handle(new TestICommit() { CheckpointToken = 2 }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - } - } - - [Fact] - public void verify_timeout_on_missing_commit_elapsed() - { - var sut = InitCommitSequencer(); - - DateTime start = DateTime.Now; - var result = sut.Handle(new TestICommit() { CheckpointToken = 1 }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - using (DateTimeService.Override(start)) - { - result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); - result.Should().Be(PollingClient2.HandlingResult.Retry); - } - using (DateTimeService.Override(start.AddMilliseconds(_outOfSequenceTimeoutInMilliseconds + 100))) - { - result = sut.Handle(new TestICommit() { CheckpointToken = 3 }); - result.Should().Be(PollingClient2.HandlingResult.MoveToNext); - } - } - - public class TestICommit : ICommit - { - public string BucketId - { - get { return ""; } - } - - public string StreamId - { - get { return ""; } - } - - public int StreamRevision - { - get { return 0; } - } - - public Guid CommitId - { - get { return Guid.Empty; } - } - - public int CommitSequence - { - get { return 0; } - } - - public DateTime CommitStamp - { - get { return DateTimeService.Now; } - } - - public IDictionary Headers - { - get { return new ConcurrentDictionary(); } - } - - public ICollection Events - { - get { return new List(); } - } - - public Int64 CheckpointToken { get; set; } - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/Client/PollingClient2Tests.cs b/src/NEventStore.Tests/Client/PollingClient2Tests.cs deleted file mode 100644 index ff03faca8..000000000 --- a/src/NEventStore.Tests/Client/PollingClient2Tests.cs +++ /dev/null @@ -1,294 +0,0 @@ -#pragma warning disable IDE1006 // Naming Styles - -using FakeItEasy; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.PollingClient -{ -#if MSTEST - [TestClass] -#endif - public class CreatingPollingClient2Tests - { - [Fact] - public void When_persist_streams_is_null_then_should_throw() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Catch.Exception(() => new PollingClient2(null, _ => PollingClient2.HandlingResult.MoveToNext)).Should().BeOfType(); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void When_interval_less_than_zero_then_should_throw() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Catch.Exception(() => new PollingClient2(A.Fake(), null)).Should().BeOfType(); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - } - -#if MSTEST - [TestClass] -#endif - public class base_handling_committed_events : using_polling_client2 - { - private readonly List commits = []; - - protected override void Context() - { - base.Context(); - HandleFunction = c => - { - commits.Add(c); - return PollingClient2.HandlingResult.MoveToNext; - }; - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.StartFrom(0); - } - - [Fact] - public void commits_are_correctly_dispatched() - { - WaitForCondition(() => commits.Count >= 1); - commits.Count.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class base_handling_committed_events_and_new_events : using_polling_client2 - { - private readonly List commits = []; - - protected override void Context() - { - base.Context(); - HandleFunction = c => - { - commits.Add(c); - return PollingClient2.HandlingResult.MoveToNext; - }; - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.StartFrom(0); - for (int i = 0; i < 15; i++) - { - StoreEvents.Advanced.CommitSingle(); - } - } - - [Fact] - public void commits_are_correctly_dispatched() - { - WaitForCondition(() => commits.Count >= 16); - commits.Count.Should().Be(16); - } - } - -#if MSTEST - [TestClass] -#endif - public class verify_stopping_commit_polling_client : using_polling_client2 - { - private readonly List commits = []; - - protected override void Context() - { - base.Context(); - HandleFunction = c => - { - commits.Add(c); - return PollingClient2.HandlingResult.Stop; - }; - StoreEvents.Advanced.CommitSingle(); - StoreEvents.Advanced.CommitSingle(); - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.StartFrom(0); - } - - [Fact] - public void commits_are_correctly_dispatched() - { - WaitForCondition(() => commits.Count >= 2, timeoutInSeconds: 1); - commits.Count.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class verify_retry_commit_polling_client : using_polling_client2 - { - private readonly List commits = []; - - protected override void Context() - { - base.Context(); - HandleFunction = c => - { - commits.Add(c); - if (commits.Count < 3) - return PollingClient2.HandlingResult.Retry; - - return PollingClient2.HandlingResult.MoveToNext; - }; - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.StartFrom(0); - } - - [Fact] - public void commits_are_retried() - { - WaitForCondition(() => commits.Count >= 3); - commits.Count.Should().Be(3); - commits.All(c => c.CheckpointToken == 1).Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class verify_retry_then_move_next : using_polling_client2 - { - private readonly List commits = []; - - protected override void Context() - { - base.Context(); - HandleFunction = c => - { - commits.Add(c); - if (commits.Count < 3 && c.CheckpointToken == 1) - return PollingClient2.HandlingResult.Retry; - - return PollingClient2.HandlingResult.MoveToNext; - }; - StoreEvents.Advanced.CommitSingle(); - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.StartFrom(0); - } - - [Fact] - public void commits_are_retried_then_move_next() - { - WaitForCondition(() => commits.Count >= 4, timeoutInSeconds: 1); - commits.Count.Should().Be(4); - commits - .Select(c => c.CheckpointToken) - .SequenceEqual([1L, 1L, 1, 2]) - .Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class verify_manual_polling : using_polling_client2 - { - private readonly List commits = []; - - protected override void Context() - { - base.Context(); - HandleFunction = c => - { - commits.Add(c); - return PollingClient2.HandlingResult.MoveToNext; - }; - StoreEvents.Advanced.CommitSingle(); - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Because() - { - Sut.ConfigurePollingFunction(); - Sut.PollNow(); - } - - [Fact] - public void commits_are_retried_then_move_next() - { - WaitForCondition(() => commits.Count >= 2, timeoutInSeconds: 3); - commits.Count.Should().Be(2); - commits - .Select(c => c.CheckpointToken) - .SequenceEqual([1L, 2L]) - .Should().BeTrue(); - } - } - - public abstract class using_polling_client2 : SpecificationBase - { - protected const int PollingInterval = 100; - protected PollingClient2? sut; - private IStoreEvents? _storeEvents; - - protected PollingClient2 Sut - { - get { return sut!; } - } - - protected IStoreEvents StoreEvents - { - get { return _storeEvents!; } - } - - protected Func? HandleFunction; - - protected override void Context() - { - HandleFunction = _ => PollingClient2.HandlingResult.MoveToNext; - _storeEvents = Wireup.Init().UsingInMemoryPersistence().Build(); - sut = new PollingClient2(_storeEvents.Advanced, c => HandleFunction(c), PollingInterval); - } - - protected override void Cleanup() - { - _storeEvents?.Dispose(); - Sut.Dispose(); - } - - protected void WaitForCondition(Func predicate, Int32 timeoutInSeconds = 4) - { - DateTime startTest = DateTime.Now; - while (!predicate() && DateTime.Now.Subtract(startTest).TotalSeconds < timeoutInSeconds) - { - Thread.Sleep(100); - } - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/Client/PollingClientRx.cs b/src/NEventStore.Tests/Client/PollingClientRx.cs deleted file mode 100644 index e20df0de4..000000000 --- a/src/NEventStore.Tests/Client/PollingClientRx.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace NEventStore.PollingClient -{ - using System; - using System.Reactive.Subjects; - using NEventStore.Persistence; - - /// - /// Represents a client that poll the storage for latest commits. - /// - public sealed class PollingClientRx - { - private readonly PollingClient2 _pollingClient2; - - private readonly Subject _subject; - - public PollingClientRx( - IPersistStreams persistStreams, - Int32 waitInterval = 5000) - { - if (persistStreams is null) - { - throw new ArgumentNullException(nameof(persistStreams)); - } - if (waitInterval <= 0) - { - throw new ArgumentException("Must be greater than 0", nameof(waitInterval)); - } - _subject = new Subject(); - _pollingClient2 = new PollingClient2(persistStreams, c => - { - _subject.OnNext(c); - return PollingClient2.HandlingResult.MoveToNext; - }, - waitInterval: waitInterval); - } - - public IDisposable Subscribe(IObserver observer) - { - return _subject.Subscribe(observer); - } - - private Int64 _checkpointToObserveFrom; - - public IObservable ObserveFrom(Int64 checkpointToken = 0) - { - _checkpointToObserveFrom = checkpointToken; - return _subject; - } - - internal void Start() - { - _pollingClient2.StartFrom(_checkpointToObserveFrom); - } - - internal void Dispose() - { - _pollingClient2.Dispose(); - } - - internal void StartFromBucket(string bucketId) - { - _pollingClient2.StartFromBucket(bucketId, 0); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Tests/Client/PollingClientRxTests.cs b/src/NEventStore.Tests/Client/PollingClientRxTests.cs deleted file mode 100644 index 4a90b8c2f..000000000 --- a/src/NEventStore.Tests/Client/PollingClientRxTests.cs +++ /dev/null @@ -1,279 +0,0 @@ -#pragma warning disable IDE1006 // Naming Styles -using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; -using FakeItEasy; -using FluentAssertions; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.PollingClient -{ -#if MSTEST - [TestClass] -#endif - public class CreatingPollingClientTests - { - [Fact] - public void When_persist_streams_is_null_then_should_throw() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Catch.Exception(() => new PollingClientRx(null)).Should().BeOfType(); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void When_interval_less_than_zero_then_should_throw() - { - Catch.Exception(() => new PollingClientRx(A.Fake(), -1)).Should().BeOfType(); - } - - [Fact] - public void When_interval_is_zero_then_should_throw() - { - Catch.Exception(() => new PollingClientRx(A.Fake(), 0)).Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_commit_is_committed_before_subscribing : using_polling_client - { - private IObservable? _observeCommits; - private Task? _commitObserved; - - protected override void Context() - { - base.Context(); - StoreEvents.Advanced.CommitSingle(); - _observeCommits = PollingClient.ObserveFrom(); - _commitObserved = _observeCommits.FirstAsync().ToTask(); - } - - protected override void Because() - { - PollingClient.Start(); - } - - protected override void Cleanup() - { - PollingClient.Dispose(); - } - - [Fact] - public void should_observe_commit() - { - _commitObserved!.Wait(PollingInterval * 40).Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_commit_is_committed_before_and_after_subscribing : using_polling_client - { - private IObservable? _observeCommits; - private Task? _twoCommitsObserved; - - protected override void Context() - { - base.Context(); - StoreEvents.Advanced.CommitSingle(); - _observeCommits = PollingClient.ObserveFrom(); - _twoCommitsObserved = _observeCommits.Take(2).ToTask(); - } - - protected override void Because() - { - PollingClient.Start(); - StoreEvents.Advanced.CommitSingle(); - } - - protected override void Cleanup() - { - PollingClient.Dispose(); - } - - [Fact] - public void should_observe_two_commits() - { - _twoCommitsObserved!.Wait(PollingInterval * 40).Should().BeTrue(); - } - } - - //#if MSTEST - // [TestClass] - //#endif - //public class with_two_observers_and_multiple_commits : using_polling_client - //{ - // private IObserveCommits _observeCommits1; - // private IObserveCommits _observeCommits2; - // private Task _observeCommits1Complete; - // private Task _observeCommits2Complete; - - // protected override void Context() - // { - // base.Context(); - // StoreEvents.Advanced.CommitSingle(); - // _observeCommits1 = PollingClient.ObserveFrom(); - // _observeCommits1Complete = _observeCommits1.Take(5).ToTask(); - - // _observeCommits2 = PollingClient.ObserveFrom(); - // _observeCommits2Complete = _observeCommits1.Take(10).ToTask(); - // } - - // protected override void Because() - // { - // _observeCommits1.Start(); - // _observeCommits2.Start(); - // Task.Factory.StartNew(() => - // { - // for (int i = 0; i < 15; i++) - // { - // StoreEvents.Advanced.CommitSingle(); - // } - // }); - // } - - // protected override void Cleanup() - // { - // _observeCommits1.Dispose(); - // _observeCommits2.Dispose(); - // } - - // [Fact] - // public void should_observe_commits_on_first_observer() - // { - // _observeCommits1Complete.Wait(PollingInterval * 10).ShouldBe(true); - // } - - // [Fact] - // public void should_observe_commits_on_second_observer() - // { - // _observeCommits2Complete.Wait(PollingInterval * 10).ShouldBe(true); - // } - //} - -#if MSTEST - [TestClass] -#endif - public class with_two_subscriptions_on_a_single_observer_and_multiple_commits : using_polling_client - { - private IObservable? _observeCommits1; - private Task? _observeCommits1Complete; - private Task? _observeCommits2Complete; - - protected override void Context() - { - base.Context(); - StoreEvents.Advanced.CommitSingle(); - _observeCommits1 = PollingClient.ObserveFrom(); - _observeCommits1Complete = _observeCommits1.Take(5).ToTask(); - _observeCommits2Complete = _observeCommits1.Take(10).ToTask(); - } - - protected override void Because() - { - PollingClient.Start(); - Task.Factory.StartNew(() => - { - for (int i = 0; i < 15; i++) - { - StoreEvents.Advanced.CommitSingle(); - } - }); - } - - protected override void Cleanup() - { - PollingClient.Dispose(); - } - - [Fact] - public void should_observe_commits_on_first_observer() - { - _observeCommits1Complete!.Wait(PollingInterval * 10).Should().BeTrue(); - } - - [Fact] - public void should_observe_commits_on_second_observer() - { - _observeCommits2Complete!.Wait(PollingInterval * 10).Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_polling_from_bucket1 : using_polling_client - { - private IObservable? _observeCommits; - private Task? _commitObserved; - - protected override void Context() - { - base.Context(); - StoreEvents.Advanced.CommitMany(4, null, "bucket_2"); - StoreEvents.Advanced.CommitMany(4, null, "bucket_1"); - _observeCommits = PollingClient.ObserveFrom(); - _commitObserved = _observeCommits.FirstAsync().ToTask(); - } - - protected override void Because() - { - PollingClient.StartFromBucket("bucket_1"); - } - - protected override void Cleanup() - { - PollingClient.Dispose(); - } - - [Fact] - public void should_observe_commit_from_bucket1() - { - _commitObserved!.Wait(PollingInterval * 40).Should().BeTrue(); - _commitObserved.Result.BucketId.Should().Be("bucket_1"); - } - } - - public abstract class using_polling_client : SpecificationBase - { - protected const int PollingInterval = 100; - private PollingClientRx? _pollingClient; - private IStoreEvents? _storeEvents; - - protected PollingClientRx PollingClient - { - get { return _pollingClient!; } - } - - protected IStoreEvents StoreEvents - { - get { return _storeEvents!; } - } - - protected override void Context() - { - _storeEvents = Wireup.Init().UsingInMemoryPersistence().Build(); - _pollingClient = new PollingClientRx(_storeEvents.Advanced, PollingInterval); - } - - protected override void Cleanup() - { - _storeEvents!.Dispose(); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles \ No newline at end of file diff --git a/src/NEventStore.Tests/ConversionTests/EventUpconverterPipelineHookTests.cs b/src/NEventStore.Tests/ConversionTests/EventUpconverterPipelineHookTests.cs deleted file mode 100644 index 1e43f5655..000000000 --- a/src/NEventStore.Tests/ConversionTests/EventUpconverterPipelineHookTests.cs +++ /dev/null @@ -1,275 +0,0 @@ -#pragma warning disable IDE1006 // Naming Styles - -using System.Reflection; -using NEventStore.Conversion; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.ConversionTests -{ -#if MSTEST - [TestClass] -#endif - public class when_opening_a_commit_that_does_not_have_convertible_events : using_event_converter - { - private ICommit? _commit; - - private ICommit? _converted; - - protected override void Context() - { - _commit = CreateCommit(new EventMessage { Body = new NonConvertingEvent() }); - } - - protected override void Because() - { - _converted = EventUpconverter.SelectCommit(_commit!); - } - - [Fact] - public void should_not_be_converted() - { - _converted.Should().BeSameAs(_commit); - } - - [Fact] - public void should_have_the_same_instance_of_the_event() - { - _converted!.Events.Single().Should().Be(_commit!.Events.Single()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_a_commit_that_has_convertible_events : using_event_converter - { - private ICommit? _commit; - - private readonly Guid _id = Guid.NewGuid(); - private ICommit? _converted; - - protected override void Context() - { - _commit = CreateCommit(new EventMessage { Body = new ConvertingEvent(_id) }); - } - - protected override void Because() - { - _converted = EventUpconverter.SelectCommit(_commit!); - } - - [Fact] - public void should_be_of_the_converted_type() - { - _converted!.Events.Single().Body.GetType().Should().Be(typeof(ConvertingEvent3)); - } - - [Fact] - public void should_have_the_same_id_of_the_committed_event() - { - ((ConvertingEvent3)_converted!.Events.Single().Body).Id.Should().Be(_id); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_a_commit_that_has_mixed_convertible_events : using_event_converter - { - private readonly Guid _id = Guid.NewGuid(); - private EventMessage? _first; - private EventMessage? _third; - private ICommit? _commit; - private ICommit? _converted; - - protected override void Context() - { - _first = new EventMessage { Body = new NonConvertingEvent() }; - _third = new EventMessage { Body = new NonConvertingEvent() }; - _commit = CreateCommit( - _first, - new EventMessage { Body = new ConvertingEvent(_id) }, - _third); - } - - protected override void Because() - { - _converted = EventUpconverter.SelectCommit(_commit!); - } - - [Fact] - public void should_convert_only_the_convertible_event() - { - _converted!.Events.ElementAt(1).Body.Should().BeOfType(); - } - - [Fact] - public void should_preserve_unconverted_event_instances() - { - _converted!.Events.First().Should().BeSameAs(_first); - _converted.Events.Last().Should().BeSameAs(_third); - } - } - - // ReSharper disable InconsistentNaming -#if MSTEST - [TestClass] -#endif - public class when_an_event_converter_implements_the_IConvertEvents_interface_explicitly : using_event_converter - // ReSharper restore InconsistentNaming - { - private ICommit? _commit; - private readonly Guid _id = Guid.NewGuid(); - private ICommit? _converted; - private EventMessage? _eventMessage; - - protected override void Context() - { - _eventMessage = new EventMessage { Body = new ConvertingEvent2(_id, "FooEvent") }; - - _commit = CreateCommit(_eventMessage); - } - - protected override void Because() - { - _converted = EventUpconverter.SelectCommit(_commit!); - } - - [Fact] - public void should_be_of_the_converted_type() - { - _converted!.Events.Single().Body.GetType().Should().Be(typeof(ConvertingEvent3)); - } - - [Fact] - public void should_have_the_same_id_of_the_committed_event() - { - ((ConvertingEvent3)_converted!.Events.Single().Body).Id.Should().Be(_id); - } - } - - public abstract class using_event_converter : SpecificationBase - { - private IEnumerable? _assemblies; - private Dictionary>? _converters; - private EventUpconverterPipelineHook? _eventUpconverter; - - protected EventUpconverterPipelineHook EventUpconverter - { - get { return _eventUpconverter ??= CreateUpConverterHook(); } - } - - private EventUpconverterPipelineHook CreateUpConverterHook() - { - _assemblies = GetAllAssemblies(); - _converters = GetConverters(_assemblies); - return new EventUpconverterPipelineHook(_converters); - } - - private static Dictionary> GetConverters(IEnumerable toScan) - { - IEnumerable>> c = from a in toScan - from t in a.GetTypes() - let i = t.GetInterface(typeof(IUpconvertEvents<,>).FullName!) - where i != null - let sourceType = i.GetGenericArguments().First() - let convertMethod = i.GetMethods(BindingFlags.Public | BindingFlags.Instance).First() - let instance = Activator.CreateInstance(t) - select new KeyValuePair>(sourceType, e => convertMethod.Invoke(instance, [e])); - try - { - return c.ToDictionary(x => x.Key, x => x.Value); - } - catch (ArgumentException ex) - { - throw new MultipleConvertersFoundException(ex.Message, ex); - } - } - - private static IEnumerable GetAllAssemblies() - { - return - Assembly.GetCallingAssembly().GetReferencedAssemblies().Select(Assembly.Load).Concat([Assembly.GetCallingAssembly()]); - } - - protected static ICommit CreateCommit(params EventMessage[] eventMessages) - { - return new Commit(Bucket.Default, - Guid.NewGuid().ToString(), - eventMessages.Length, - Guid.NewGuid(), - 1, - DateTime.MinValue, - 1, - null, - eventMessages); - } - } - - public class ConvertingEventConverter : IUpconvertEvents - { - public ConvertingEvent2 Convert(ConvertingEvent sourceEvent) - { - return new ConvertingEvent2(sourceEvent.Id, "Temp"); - } - } - - public class ExplicitConvertingEventConverter : IUpconvertEvents - { - ConvertingEvent3 IUpconvertEvents.Convert(ConvertingEvent2 sourceEvent) - { - return new ConvertingEvent3(sourceEvent.Id, "Temp", true); - } - } - - public class NonConvertingEvent; - - public class ConvertingEvent - { - public ConvertingEvent(Guid id) - { - Id = id; - } - - public Guid Id { get; set; } - } - - public class ConvertingEvent2 - { - public ConvertingEvent2(Guid id, string name) - { - Id = id; - Name = name; - } - - public Guid Id { get; set; } - public string Name { get; set; } - } - - public class ConvertingEvent3 - { - public ConvertingEvent3(Guid id, string name, bool imExplicit) - { - Id = id; - Name = name; - ImExplicit = imExplicit; - } - - public Guid Id { get; set; } - public string Name { get; set; } - public bool ImExplicit { get; set; } - } -} - -#pragma warning restore IDE1006 // Naming Styles \ No newline at end of file diff --git a/src/NEventStore.Tests/DefaultSerializationWireupTests.cs b/src/NEventStore.Tests/DefaultSerializationWireupTests.cs deleted file mode 100644 index 8b9a3f7ac..000000000 --- a/src/NEventStore.Tests/DefaultSerializationWireupTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -#pragma warning disable IDE1006 // Naming Styles - -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -using NEventStore.Persistence.InMemory; -using NEventStore.Tests; -using NEventStore.Persistence; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore -{ -#if MSTEST - [TestClass] -#endif - public class when_building_an_event_store_without_an_explicit_serializer : SpecificationBase - { - private TestableWireup? _wireup; - private Exception? _exception; - private IStoreEvents? _eventStore; - - protected override void Context() - { - _wireup = Wireup.Init() - // .UsingInMemoryPersistence() // the InMemoryPersistence should be the default serializer - .UseTestableWireup(); - } - - protected override void Because() - { - _exception = Catch.Exception(() => _eventStore = _wireup!.Build()); - } - - protected override void Cleanup() - { - _eventStore?.Dispose(); - } - - [Fact] - public void should_not_throw_an_argument_null_exception() - { - // _exception.Should().NotBeOfType(); - _exception.Should().BeNull(); - } - - [Fact] - public void should_have_InMemoryPersistenceEngine_as_default_serializer() - { - var defaultPersistence = _wireup!.Container.Resolve(); - defaultPersistence.Should().BeOfType(typeof(InMemoryPersistenceEngine)); - - // cannot check eventstore.Advanced type because the persistence is wrapped - // by a PipelineHooksPersistenceAwareDecorator - // _eventStore.Advanced.Should().BeOfType(typeof(InMemoryPersistenceEngine)); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/EnumerableCounter.cs b/src/NEventStore.Tests/EnumerableCounter.cs deleted file mode 100644 index 4e4c77287..000000000 --- a/src/NEventStore.Tests/EnumerableCounter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections; - -namespace NEventStore -{ - internal class EnumerableCounter : IEnumerable - { - private readonly IEnumerable _enumerable; - - public EnumerableCounter(IEnumerable enumerable) - { - _enumerable = enumerable; - GetEnumeratorCallCount = 0; - } - - public int GetEnumeratorCallCount { get; private set; } - - public IEnumerator GetEnumerator() - { - GetEnumeratorCallCount++; - return _enumerable.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Tests/EventMessageTests.cs b/src/NEventStore.Tests/EventMessageTests.cs deleted file mode 100644 index a2849086c..000000000 --- a/src/NEventStore.Tests/EventMessageTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore -{ -#if MSTEST - [TestClass] -#endif - public class when_creating_a_new_event_message - { - [Fact] - public void should_expose_a_non_null_headers_dictionary() - { - var message = new EventMessage(); - - message.Headers.Should().NotBeNull(); - } - - [Fact] - public void should_allow_mutating_the_headers_dictionary() - { - var message = new EventMessage(); - - message.Headers["key"] = "value"; - - message.Headers["key"].Should().Be("value"); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/NEventStore.Core.Tests.csproj b/src/NEventStore.Tests/NEventStore.Core.Tests.csproj deleted file mode 100644 index 0eb961de5..000000000 --- a/src/NEventStore.Tests/NEventStore.Core.Tests.csproj +++ /dev/null @@ -1,40 +0,0 @@ - - - - net472;$(ModernTargetFrameworks) - false - false - NEventStore.Tests - NEventStore.Tests - true - - - - TRACE;DEBUG;NUNIT - NUNIT - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/NEventStore.Tests/NUnitTestAdapterTestsDiscovery.cs b/src/NEventStore.Tests/NUnitTestAdapterTestsDiscovery.cs deleted file mode 100644 index cba13cb9f..000000000 --- a/src/NEventStore.Tests/NUnitTestAdapterTestsDiscovery.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NUnit.Framework; - -namespace NEventStore.Tests -{ - /// - /// this is needed to allow NUnit test adapter to discover the tests, - /// if we do not have any class explicitly marked with the TestFixture attribute - /// all the assembly will be ignored (even if some class inherit from something which is - /// marked with the attribute in a reference assembly) - /// -#if NUNIT - [TestFixture] - public class NUnitTestAdapterTestsDiscovery - { - } -#endif -} diff --git a/src/NEventStore.Tests/OptimisticEventStoreTests.Async.cs b/src/NEventStore.Tests/OptimisticEventStoreTests.Async.cs deleted file mode 100644 index ca4a92cc9..000000000 --- a/src/NEventStore.Tests/OptimisticEventStoreTests.Async.cs +++ /dev/null @@ -1,752 +0,0 @@ - -#pragma warning disable 169 // ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -using FluentAssertions; -using FakeItEasy; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using System.IO; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.Async -{ -#if MSTEST - [TestClass] -#endif - public class when_creating_a_new_stream : using_persistence - { - private IEventStream? _stream; - - protected override void Because() - { - _stream = Store.CreateStream(streamId); - } - - [Fact] - public void should_return_a_new_stream() - { - _stream.Should().NotBeNull(); - } - - [Fact] - public void should_return_a_stream_with_the_correct_stream_identifier() - { - _stream!.StreamId.Should().Be(streamId); - } - - [Fact] - public void should_return_a_stream_with_a_zero_stream_revision() - { - _stream!.StreamRevision.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_a_zero_commit_sequence() - { - _stream!.CommitSequence.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_no_uncommitted_events() - { - _stream!.UncommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_return_a_stream_with_no_committed_events() - { - _stream!.CommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_return_a_stream_with_empty_headers() - { - _stream!.UncommittedHeaders.Should().BeEmpty(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_an_empty_stream_starting_at_revision_zero : using_persistence - { - private IEventStream? _stream; - - protected override void Context() - { - // read an empty stream! - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, 0, A>.Ignored, CancellationToken.None)) - .Returns(Task.CompletedTask); - } - - protected override async Task BecauseAsync() - { - _stream = await Store.OpenStreamAsync(streamId, 0, 0, CancellationToken.None).ConfigureAwait(false); - } - - [Fact] - public void should_return_a_new_stream() - { - _stream.Should().NotBeNull(); - } - - [Fact] - public void should_return_a_stream_with_the_correct_stream_identifier() - { - _stream!.StreamId.Should().Be(streamId); - } - - [Fact] - public void should_return_a_stream_with_a_zero_stream_revision() - { - _stream!.StreamRevision.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_a_zero_commit_sequence() - { - _stream!.CommitSequence.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_no_uncommitted_events() - { - _stream!.UncommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_return_a_stream_with_no_committed_events() - { - _stream!.CommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_return_a_stream_with_empty_headers() - { - _stream!.UncommittedHeaders.Should().BeEmpty(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_an_empty_stream_starting_above_revision_zero : using_persistence - { - private const int MinRevision = 1; - private Exception? _thrown; - - protected override void Context() - { - // read an empty stream! - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, MinRevision, int.MaxValue, A>.Ignored, CancellationToken.None)) - .Returns(Task.CompletedTask); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Store.OpenStreamAsync(streamId, MinRevision)).ConfigureAwait(false); - } - - [Fact] - public void should_throw_a_StreamNotFoundException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_a_populated_stream : using_persistence - { - private const int MinRevision = 17; - private const int MaxRevision = 42; - private ICommit? _committed; - private IEventStream? _stream; - - protected override void Context() - { - _committed = BuildCommitStub(1, MinRevision, 1); - - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_committed, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - - var hook = A.Fake(); - A.CallTo(() => hook.SelectCommit(_committed)).Returns(_committed); - PipelineHooks.Add(hook); - - var hookAsync = A.Fake(); - A.CallTo(() => hookAsync.SelectCommitAsync(_committed, A.Ignored)).Returns(Task.FromResult(_committed)); - PipelineHooksAsync.Add(hookAsync); - } - - protected override async Task BecauseAsync() - { - _stream = await Store.OpenStreamAsync(streamId, MinRevision, MaxRevision).ConfigureAwait(false); - } - - [Fact] - public void should_invoke_the_underlying_infrastructure_with_the_values_provided() - { - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_provide_the_commits_to_the_selection_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.SelectCommit(_committed!)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_provide_the_commits_to_the_async_selection_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.SelectCommitAsync(_committed!, A.Ignored)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_return_an_event_stream_containing_the_correct_stream_identifier() - { - _stream!.StreamId.Should().Be(streamId); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_a_populated_stream_from_a_snapshot : using_persistence - { - private const int MaxRevision = int.MaxValue; - private ICommit[]? _committed; - private Snapshot? _snapshot; - - protected override void Context() - { - _snapshot = new Snapshot(streamId, 42, "snapshot"); - _committed = [BuildCommitStub(1, 42, 0)]; - - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 42, MaxRevision, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - return Store.OpenStreamAsync(_snapshot!, MaxRevision, CancellationToken.None); - } - - [Fact] - public void should_query_the_underlying_storage_using_the_revision_of_the_snapshot() - { - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 42, MaxRevision, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_a_stream_from_a_snapshot_that_is_at_the_revision_of_the_stream_head : using_persistence - { - private const int HeadStreamRevision = 42; - private const int HeadCommitSequence = 15; - private EnumerableCounter? _committed; - private Snapshot? _snapshot; - private IEventStream? _stream; - - protected override void Context() - { - _snapshot = new Snapshot(streamId, HeadStreamRevision, "snapshot"); - _committed = new EnumerableCounter( - [BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence)]); - - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, HeadStreamRevision, int.MaxValue, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override async Task BecauseAsync() - { - _stream = await Store.OpenStreamAsync(_snapshot!, int.MaxValue, CancellationToken.None).ConfigureAwait(false); - } - - [Fact] - public void should_return_a_stream_with_the_correct_stream_identifier() - { - _stream!.StreamId.Should().Be(streamId); - } - - [Fact] - public void should_return_a_stream_with_revision_of_the_stream_head() - { - _stream!.StreamRevision.Should().Be(HeadStreamRevision); - } - - [Fact] - public void should_return_a_stream_with_a_commit_sequence_of_the_stream_head() - { - _stream!.CommitSequence.Should().Be(HeadCommitSequence); - } - - [Fact] - public void should_return_a_stream_with_no_committed_events() - { - _stream!.CommittedEvents.Count.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_no_uncommitted_events() - { - _stream!.UncommittedEvents.Count.Should().Be(0); - } - - [Fact] - public void should_only_enumerate_the_set_of_commits_once() - { - _committed!.GetEnumeratorCallCount.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_revision_zero : using_persistence - { - protected override void Context() - { - - // read an empty stream! - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) - .Returns(Task.CompletedTask); - } - - protected override Task BecauseAsync() - { - return Store.GetFromAsync(streamId, 0, int.MaxValue, new CommitStreamObserver(), CancellationToken.None); - } - - [Fact] - public void should_pass_a_revision_range_to_the_persistence_infrastructure() - { - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_up_to_revision_revision_zero : using_persistence - { - private ICommit? _committed; - - protected override void Context() - { - _committed = BuildCommitStub(1, 1, 1); - - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_committed, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - return Store.OpenStreamAsync(streamId, 0, 0); - } - - [Fact] - public void should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure() - { - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_a_null_snapshot : using_persistence - { - private Exception? thrown; - - protected override async Task BecauseAsync() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - thrown = await Catch.ExceptionAsync(() => Store.OpenStreamAsync(null , int.MaxValue, CancellationToken.None)).ConfigureAwait(false); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void should_throw_an_ArgumentNullException() - { - thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_a_snapshot_up_to_revision_revision_zero : using_persistence - { - private ICommit? _committed; - private Snapshot? snapshot; - - protected override void Context() - { - snapshot = new Snapshot(streamId, 1, "snapshot"); - _committed = BuildCommitStub(1, 1, 1); - - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, snapshot.StreamRevision, int.MaxValue, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_committed, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - return Store.OpenStreamAsync(snapshot!, 0, CancellationToken.None); - } - - [Fact] - public void should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure() - { - A.CallTo(() => Persistence.GetFromAsync(Bucket.Default, streamId, snapshot!.StreamRevision, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_a_null_attempt_back_to_the_stream : using_persistence - { - private Exception? thrown; - - protected override async Task BecauseAsync() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - thrown = await Catch.ExceptionAsync(() => Store.CommitAsync(null, CancellationToken.None)).ConfigureAwait(false); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void should_throw_an_ArgumentNullException() - { - thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_with_a_valid_and_populated_attempt_to_a_stream : using_persistence - { - private CommitAttempt? _populatedAttempt; - private ICommit? _populatedCommit; - - protected override void Context() - { - _populatedAttempt = BuildCommitAttemptStub(1, 1); - - A.CallTo(() => Persistence.CommitAsync(_populatedAttempt, CancellationToken.None)) - .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => - { - _populatedCommit = new Commit(attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events); - return _populatedCommit; - }); - - var hook = A.Fake(); - A.CallTo(() => hook.PreCommit(_populatedAttempt)).Returns(true); - PipelineHooks.Add(hook); - - var hookAsync = A.Fake(); - A.CallTo(() => hookAsync.PreCommitAsync(_populatedAttempt, A.Ignored)).Returns(Task.FromResult(true)); - PipelineHooksAsync.Add(hookAsync); - } - - protected override Task BecauseAsync() - { - return Store.CommitAsync(_populatedAttempt!, CancellationToken.None); - } - - [Fact] - public void should_provide_the_commit_to_the_PreCommit_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.PreCommit(_populatedAttempt!)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_provide_the_commit_to_the_async_PreCommit_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PreCommitAsync(_populatedAttempt!, A.Ignored)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_provide_the_commit_attempt_to_the_configured_persistence_mechanism() - { - A.CallTo(() => Persistence.CommitAsync(_populatedAttempt!, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_provide_the_commit_to_the_PostCommit_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_populatedCommit!)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_provide_the_commit_to_the_async_PostCommit_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_populatedCommit!, A.Ignored)).MustHaveHappenedOnceExactly()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_a_PreCommit_hook_rejects_a_commit : using_persistence - { - private CommitAttempt? _attempt; - private ICommit? _commit; - - protected override void Context() - { - _attempt = BuildCommitAttemptStub(1, 1); - _commit = BuildCommitStub(1, 1, 1); - - var hook = A.Fake(); - A.CallTo(() => hook.PreCommit(_attempt)).Returns(false); - PipelineHooks.Add(hook); - - var hookAsync = A.Fake(); - A.CallTo(() => hookAsync.PreCommitAsync(_attempt, A.Ignored)).Returns(Task.FromResult(true)); - PipelineHooksAsync.Add(hookAsync); - } - - protected override Task BecauseAsync() - { - return Store.CommitAsync(_attempt!, CancellationToken.None); - } - - [Fact] - public void should_not_call_the_underlying_infrastructure() - { - A.CallTo(() => Persistence.CommitAsync(_attempt!, CancellationToken.None)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_provide_the_commit_to_the_PostCommit_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_commit!)).MustNotHaveHappened()); - } - - [Fact] - public void should_not_provide_the_commit_to_the_async_PostCommit_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_an_async_PreCommit_hook_rejects_a_commit : using_persistence - { - private CommitAttempt? _attempt; - private ICommit? _commit; - - protected override void Context() - { - _attempt = BuildCommitAttemptStub(1, 1); - _commit = BuildCommitStub(1, 1, 1); - - var hook = A.Fake(); - A.CallTo(() => hook.PreCommit(_attempt)).Returns(true); - PipelineHooks.Add(hook); - - var hookAsync = A.Fake(); - A.CallTo(() => hookAsync.PreCommitAsync(_attempt, A.Ignored)).Returns(Task.FromResult(false)); - PipelineHooksAsync.Add(hookAsync); - } - - protected override Task BecauseAsync() - { - return Store.CommitAsync(_attempt!, CancellationToken.None); - } - - [Fact] - public void should_not_call_the_underlying_infrastructure() - { - A.CallTo(() => Persistence.CommitAsync(_attempt!, CancellationToken.None)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_provide_the_commit_to_the_PostCommit_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_commit!)).MustNotHaveHappened()); - } - - [Fact] - public void should_not_provide_the_commit_to_the_async_PostCommit_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_accessing_the_underlying_persistence_with_pipeline_hooks : using_persistence - { - protected override void Because() - { - PipelineHooks.Add(A.Fake()); - } - - [Fact] - public void should_return_a_reference_to_the_underlying_persistence_infrastructure_decorator() - { - Store.Advanced.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_accessing_the_underlying_persistence_with_async_pipeline_hooks : using_persistence - { - protected override void Because() - { - PipelineHooksAsync.Add(A.Fake()); - } - - [Fact] - public void should_return_a_reference_to_the_underlying_persistence_infrastructure_decorator() - { - Store.Advanced.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_accessing_the_underlying_persistence_without_pipeline_hooks : using_persistence - { - [Fact] - public void should_return_a_reference_to_the_underlying_persistence() - { - Store.Advanced.Should().BeOfType(Persistence.GetType()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_disposing_the_event_store : using_persistence - { - protected override void Because() - { - Store.Dispose(); - } - - [Fact] - public void should_dispose_the_underlying_persistence() - { - A.CallTo(() => Persistence.Dispose()).MustHaveHappenedOnceExactly(); - } - } - - public abstract class using_persistence : SpecificationBase - { - private IPersistStreams? persistence; - - private List? pipelineHooks; - private List? pipelineHooksAsync; - private OptimisticEventStore? store; - protected string streamId = Guid.NewGuid().ToString(); - - protected IPersistStreams Persistence - { - get { return persistence ??= A.Fake(); } - } - - protected List PipelineHooks - { - get { return pipelineHooks ??= []; } - } - - protected List PipelineHooksAsync - { - get { return pipelineHooksAsync ??= []; } - } - - protected OptimisticEventStore Store - { - get { return store ??= new OptimisticEventStore(Persistence, PipelineHooks.Select(x => x), PipelineHooksAsync.Select(x => x)); } - } - - protected override void Cleanup() - { - streamId = Guid.NewGuid().ToString(); - } - - protected CommitAttempt BuildCommitAttemptStub(Guid commitId) - { - return new CommitAttempt(Bucket.Default, streamId, 1, commitId, 1, SystemTime.UtcNow, null, []); - } - - protected ICommit BuildCommitStub(long checkpointToken, int streamRevision, int commitSequence) - { - List events = new[] { new EventMessage() }.ToList(); - return new Commit(Bucket.Default, streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, checkpointToken, null, events); - } - - protected CommitAttempt BuildCommitAttemptStub(int streamRevision, int commitSequence) - { - var events = new[] { new EventMessage() }; - return new CommitAttempt(Bucket.Default, streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, null, events); - } - - protected ICommit BuildCommitStub(long checkpointToken, Guid commitId, int streamRevision, int commitSequence) - { - List events = new[] { new EventMessage() }.ToList(); - return new Commit(Bucket.Default, streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, checkpointToken, null, events); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles -#pragma warning restore 169 // ReSharper enable InconsistentNaming \ No newline at end of file diff --git a/src/NEventStore.Tests/OptimisticEventStoreTests.cs b/src/NEventStore.Tests/OptimisticEventStoreTests.cs deleted file mode 100644 index 2666f0653..000000000 --- a/src/NEventStore.Tests/OptimisticEventStoreTests.cs +++ /dev/null @@ -1,720 +0,0 @@ - -#pragma warning disable 169 // ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -using FluentAssertions; -using FakeItEasy; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore -{ -#if MSTEST - [TestClass] -#endif - public class when_creating_a_new_stream : using_persistence - { - private IEventStream? _stream; - - protected override void Because() - { - _stream = Store.CreateStream(streamId); - } - - [Fact] - public void should_return_a_new_stream() - { - _stream.Should().NotBeNull(); - } - - [Fact] - public void should_return_a_stream_with_the_correct_stream_identifier() - { - _stream!.StreamId.Should().Be(streamId); - } - - [Fact] - public void should_return_a_stream_with_a_zero_stream_revision() - { - _stream!.StreamRevision.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_a_zero_commit_sequence() - { - _stream!.CommitSequence.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_no_uncommitted_events() - { - _stream!.UncommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_return_a_stream_with_no_committed_events() - { - _stream!.CommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_return_a_stream_with_empty_headers() - { - _stream!.UncommittedHeaders.Should().BeEmpty(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_an_empty_stream_starting_at_revision_zero : using_persistence - { - private IEventStream? _stream; - - protected override void Context() - { - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, 0)).Returns([]); - } - - protected override void Because() - { - _stream = Store.OpenStream(streamId, 0, 0); - } - - [Fact] - public void should_return_a_new_stream() - { - _stream.Should().NotBeNull(); - } - - [Fact] - public void should_return_a_stream_with_the_correct_stream_identifier() - { - _stream!.StreamId.Should().Be(streamId); - } - - [Fact] - public void should_return_a_stream_with_a_zero_stream_revision() - { - _stream!.StreamRevision.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_a_zero_commit_sequence() - { - _stream!.CommitSequence.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_no_uncommitted_events() - { - _stream!.UncommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_return_a_stream_with_no_committed_events() - { - _stream!.CommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_return_a_stream_with_empty_headers() - { - _stream!.UncommittedHeaders.Should().BeEmpty(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_an_empty_stream_starting_above_revision_zero : using_persistence - { - private const int MinRevision = 1; - private Exception? _thrown; - - protected override void Context() - { - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, MinRevision, int.MaxValue)) - .Returns([]); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Store.OpenStream(streamId, MinRevision)); - } - - [Fact] - public void should_throw_a_StreamNotFoundException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_a_populated_stream : using_persistence - { - private const int MinRevision = 17; - private const int MaxRevision = 42; - private ICommit? _committed; - private IEventStream? _stream; - - protected override void Context() - { - _committed = BuildCommitStub(1, MinRevision, 1); - - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, MinRevision, MaxRevision)) - .Returns([_committed]); - - var hook = A.Fake(); - A.CallTo(() => hook.SelectCommit(_committed)).Returns(_committed); - PipelineHooks.Add(hook); - - var hookAsync = A.Fake(); - A.CallTo(() => hookAsync.SelectCommitAsync(_committed, A.Ignored)).Returns(Task.FromResult(_committed)); - PipelineHooksAsync.Add(hookAsync); - } - - protected override void Because() - { - _stream = Store.OpenStream(streamId, MinRevision, MaxRevision); - } - - [Fact] - public void should_invoke_the_underlying_infrastructure_with_the_values_provided() - { - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, MinRevision, MaxRevision)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_provide_the_commits_to_the_selection_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.SelectCommit(_committed!)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_provide_the_commits_to_the_async_selection_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.SelectCommitAsync(_committed!, A.Ignored)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_return_an_event_stream_containing_the_correct_stream_identifier() - { - _stream!.StreamId.Should().Be(streamId); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_a_populated_stream_from_a_snapshot : using_persistence - { - private const int MaxRevision = int.MaxValue; - private ICommit[]? _committed; - private Snapshot? _snapshot; - - protected override void Context() - { - _snapshot = new Snapshot(streamId, 42, "snapshot"); - _committed = [BuildCommitStub(1, 42, 0)]; - - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 42, MaxRevision)).Returns(_committed); - } - - protected override void Because() - { - Store.OpenStream(_snapshot!, MaxRevision); - } - - [Fact] - public void should_query_the_underlying_storage_using_the_revision_of_the_snapshot() - { - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 42, MaxRevision)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_opening_a_stream_from_a_snapshot_that_is_at_the_revision_of_the_stream_head : using_persistence - { - private const int HeadStreamRevision = 42; - private const int HeadCommitSequence = 15; - private EnumerableCounter? _committed; - private Snapshot? _snapshot; - private IEventStream? _stream; - - protected override void Context() - { - _snapshot = new Snapshot(streamId, HeadStreamRevision, "snapshot"); - _committed = new EnumerableCounter( - [BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence)]); - - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, HeadStreamRevision, int.MaxValue)) - .Returns(_committed); - } - - protected override void Because() - { - _stream = Store.OpenStream(_snapshot!, int.MaxValue); - } - - [Fact] - public void should_return_a_stream_with_the_correct_stream_identifier() - { - _stream!.StreamId.Should().Be(streamId); - } - - [Fact] - public void should_return_a_stream_with_revision_of_the_stream_head() - { - _stream!.StreamRevision.Should().Be(HeadStreamRevision); - } - - [Fact] - public void should_return_a_stream_with_a_commit_sequence_of_the_stream_head() - { - _stream!.CommitSequence.Should().Be(HeadCommitSequence); - } - - [Fact] - public void should_return_a_stream_with_no_committed_events() - { - _stream!.CommittedEvents.Count.Should().Be(0); - } - - [Fact] - public void should_return_a_stream_with_no_uncommitted_events() - { - _stream!.UncommittedEvents.Count.Should().Be(0); - } - - [Fact] - public void should_only_enumerate_the_set_of_commits_once() - { - _committed!.GetEnumeratorCallCount.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_revision_zero : using_persistence - { - protected override void Context() - { - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, int.MaxValue)) - .Returns([]); - } - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // This forces the enumeration of the commits. - Store.GetFrom(streamId, 0, int.MaxValue).ToList(); - } - - [Fact] - public void should_pass_a_revision_range_to_the_persistence_infrastructure() - { - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, int.MaxValue)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_up_to_revision_revision_zero : using_persistence - { - private ICommit? _committed; - - protected override void Context() - { - _committed = BuildCommitStub(1, 1, 1); - - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, int.MaxValue)).Returns([_committed]); - } - - protected override void Because() - { - Store.OpenStream(streamId, 0, 0); - } - - [Fact] - public void should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure() - { - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, 0, int.MaxValue)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_a_null_snapshot : using_persistence - { - private Exception? thrown; - - protected override void Because() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - thrown = Catch.Exception(() => Store.OpenStream(null, int.MaxValue)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void should_throw_an_ArgumentNullException() - { - thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_from_a_snapshot_up_to_revision_revision_zero : using_persistence - { - private ICommit? _committed; - private Snapshot? snapshot; - - protected override void Context() - { - snapshot = new Snapshot(streamId, 1, "snapshot"); - _committed = BuildCommitStub(1, 1, 1); - - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, snapshot.StreamRevision, int.MaxValue)) - .Returns([_committed]); - } - - protected override void Because() - { - Store.OpenStream(snapshot!, 0); - } - - [Fact] - public void should_pass_the_maximum_possible_revision_to_the_persistence_infrastructure() - { - A.CallTo(() => Persistence.GetFrom(Bucket.Default, streamId, snapshot!.StreamRevision, int.MaxValue)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_a_null_attempt_back_to_the_stream : using_persistence - { - private Exception? thrown; - - protected override void Because() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - thrown = Catch.Exception(() => Store.Commit(null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void should_throw_an_ArgumentNullException() - { - thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_with_a_valid_and_populated_attempt_to_a_stream : using_persistence - { - private CommitAttempt? _populatedAttempt; - private ICommit? _populatedCommit; - - protected override void Context() - { - _populatedAttempt = BuildCommitAttemptStub(1, 1); - - A.CallTo(() => Persistence.Commit(_populatedAttempt)) - .ReturnsLazily((CommitAttempt attempt) => - { - _populatedCommit = new Commit(attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events); - return _populatedCommit; - }); - - var hook = A.Fake(); - A.CallTo(() => hook.PreCommit(_populatedAttempt)).Returns(true); - PipelineHooks.Add(hook); - - var hookAsync = A.Fake(); - A.CallTo(() => hookAsync.PreCommitAsync(_populatedAttempt, A.Ignored)).Returns(Task.FromResult(true)); - PipelineHooksAsync.Add(hookAsync); - } - - protected override void Because() - { - Store.Commit(_populatedAttempt!); - } - - [Fact] - public void should_provide_the_commit_to_the_PreCommit_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.PreCommit(_populatedAttempt!)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_provide_the_commit_to_the_async_PreCommit_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PreCommitAsync(_populatedAttempt!, A.Ignored)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_provide_the_commit_attempt_to_the_configured_persistence_mechanism() - { - A.CallTo(() => Persistence.Commit(_populatedAttempt!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_provide_the_commit_to_the_PostCommit_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_populatedCommit!)).MustHaveHappenedOnceExactly()); - } - - [Fact] - public void should_provide_the_commit_to_the_async_PostCommit_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_populatedCommit!, A.Ignored)).MustHaveHappenedOnceExactly()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_a_PreCommit_hook_rejects_a_commit : using_persistence - { - private CommitAttempt? _attempt; - private ICommit? _commit; - - protected override void Context() - { - _attempt = BuildCommitAttemptStub(1, 1); - _commit = BuildCommitStub(1, 1, 1); - - var hook = A.Fake(); - A.CallTo(() => hook.PreCommit(_attempt)).Returns(false); - PipelineHooks.Add(hook); - - var hookAsync = A.Fake(); - A.CallTo(() => hookAsync.PreCommitAsync(_attempt, A.Ignored)).Returns(Task.FromResult(true)); - PipelineHooksAsync.Add(hookAsync); - } - - protected override void Because() - { - Store.Commit(_attempt!); - } - - [Fact] - public void should_not_call_the_underlying_infrastructure() - { - A.CallTo(() => Persistence.Commit(_attempt!)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_provide_the_commit_to_the_PostCommit_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_commit!)).MustNotHaveHappened()); - } - - [Fact] - public void should_not_provide_the_commit_to_the_async_PostCommit_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_an_async_PreCommit_hook_rejects_a_commit : using_persistence - { - private CommitAttempt? _attempt; - private ICommit? _commit; - - protected override void Context() - { - _attempt = BuildCommitAttemptStub(1, 1); - _commit = BuildCommitStub(1, 1, 1); - - var hook = A.Fake(); - A.CallTo(() => hook.PreCommit(_attempt)).Returns(true); - PipelineHooks.Add(hook); - - var hookAsync = A.Fake(); - A.CallTo(() => hookAsync.PreCommitAsync(_attempt, A.Ignored)).Returns(Task.FromResult(false)); - PipelineHooksAsync.Add(hookAsync); - } - - protected override void Because() - { - Store.Commit(_attempt!); - } - - [Fact] - public void should_not_call_the_underlying_infrastructure() - { - A.CallTo(() => Persistence.Commit(_attempt!)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_provide_the_commit_to_the_PostCommit_hooks() - { - PipelineHooks.ForEach(x => A.CallTo(() => x.PostCommit(_commit!)).MustNotHaveHappened()); - } - - [Fact] - public void should_not_provide_the_commit_to_the_async_PostCommit_hooks() - { - PipelineHooksAsync.ForEach(x => A.CallTo(() => x.PostCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_accessing_the_underlying_persistence_with_pipeline_hooks : using_persistence - { - protected override void Because() - { - PipelineHooks.Add(A.Fake()); - } - - [Fact] - public void should_return_a_reference_to_the_underlying_persistence_infrastructure_decorator() - { - Store.Advanced.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_accessing_the_underlying_persistence_with_async_pipeline_hooks : using_persistence - { - protected override void Because() - { - PipelineHooksAsync.Add(A.Fake()); - } - - [Fact] - public void should_return_a_reference_to_the_underlying_persistence_infrastructure_decorator() - { - Store.Advanced.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_accessing_the_underlying_persistence_without_pipeline_hooks : using_persistence - { - [Fact] - public void should_return_a_reference_to_the_underlying_persistence() - { - Store.Advanced.Should().BeOfType(Persistence.GetType()); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_disposing_the_event_store : using_persistence - { - protected override void Because() - { - Store.Dispose(); - } - - [Fact] - public void should_dispose_the_underlying_persistence() - { - A.CallTo(() => Persistence.Dispose()).MustHaveHappenedOnceExactly(); - } - } - - public abstract class using_persistence : SpecificationBase - { - private IPersistStreams? persistence; - - private List? pipelineHooks; - private List? pipelineHooksAsync; - private OptimisticEventStore? store; - protected string streamId = Guid.NewGuid().ToString(); - - protected IPersistStreams Persistence - { - get { return persistence ??= A.Fake(); } - } - - protected List PipelineHooks - { - get { return pipelineHooks ??= []; } - } - - protected List PipelineHooksAsync - { - get { return pipelineHooksAsync ??= []; } - } - - protected OptimisticEventStore Store - { - get { return store ??= new OptimisticEventStore(Persistence, PipelineHooks.Select(x => x), PipelineHooksAsync.Select(x => x)); } - } - - protected override void Cleanup() - { - streamId = Guid.NewGuid().ToString(); - } - - protected CommitAttempt BuildCommitAttemptStub(Guid commitId) - { - return new CommitAttempt(Bucket.Default, streamId, 1, commitId, 1, SystemTime.UtcNow, null, []); - } - - protected ICommit BuildCommitStub(long checkpointToken, int streamRevision, int commitSequence) - { - List events = new[] { new EventMessage() }.ToList(); - return new Commit(Bucket.Default, streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, checkpointToken, null, events); - } - - protected CommitAttempt BuildCommitAttemptStub(int streamRevision, int commitSequence) - { - var events = new[] { new EventMessage() }; - return new CommitAttempt(Bucket.Default, streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, null, events); - } - - protected ICommit BuildCommitStub(long checkpointToken, Guid commitId, int streamRevision, int commitSequence) - { - List events = new[] { new EventMessage() }.ToList(); - return new Commit(Bucket.Default, streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, checkpointToken, null, events); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles -#pragma warning restore 169 // ReSharper enable InconsistentNaming \ No newline at end of file diff --git a/src/NEventStore.Tests/OptimisticEventStreamDuplicateCommitTests.Async.cs b/src/NEventStore.Tests/OptimisticEventStreamDuplicateCommitTests.Async.cs deleted file mode 100644 index c28082f06..000000000 --- a/src/NEventStore.Tests/OptimisticEventStreamDuplicateCommitTests.Async.cs +++ /dev/null @@ -1,121 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore.Async -{ -#if MSTEST - [TestClass] -#endif - public class when_reusing_a_commit_identifier_after_reopening_a_stream : using_an_in_memory_store - { - private readonly Guid _duplicateCommitId = Guid.NewGuid(); - private Exception? _thrown; - - protected override async Task ContextAsync() - { - await base.ContextAsync().ConfigureAwait(false); - await SeedCommitAsync(_duplicateCommitId, "already committed").ConfigureAwait(false); - - Stream = await Store.OpenStreamAsync(StreamId, 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); - Stream.Add(new EventMessage { Body = "duplicate attempt" }); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(_duplicateCommitId, CancellationToken.None)).ConfigureAwait(false); - } - - [Fact] - public void should_throw_a_DuplicateCommitException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reusing_a_commit_identifier_from_history_that_was_not_loaded : using_an_in_memory_store - { - private readonly Guid _duplicateCommitId = Guid.NewGuid(); - private Exception? _thrown; - - protected override async Task ContextAsync() - { - await base.ContextAsync().ConfigureAwait(false); - await SeedCommitAsync(_duplicateCommitId, "first event").ConfigureAwait(false); - await SeedCommitAsync(Guid.NewGuid(), "second event").ConfigureAwait(false); - - // This stream is reopened from revision 2, so it sees the correct head revision and - // commit sequence but does not materialize the first commit at all. The duplicate check - // must therefore come from persistence, because a stream-local cache cannot observe the - // earlier commit identifier in this shape of read. - Stream = await Store.OpenStreamAsync(StreamId, 2, int.MaxValue, CancellationToken.None).ConfigureAwait(false); - Stream.Add(new EventMessage { Body = "duplicate attempt" }); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(_duplicateCommitId, CancellationToken.None)).ConfigureAwait(false); - } - - [Fact] - public void should_throw_a_DuplicateCommitException() - { - _thrown.Should().BeOfType(); - } - } - - public abstract class using_an_in_memory_store : SpecificationBase - { - private IStoreEvents? _store; - private IEventStream? _stream; - - protected string StreamId { get; } = Guid.NewGuid().ToString(); - - protected IStoreEvents Store => _store!; - - protected IEventStream Stream - { - get { return _stream!; } - set { _stream = value; } - } - - protected override Task ContextAsync() - { - // These duplicate-commit scenarios must go through a real persistence implementation. - // The production change removes the stream-local CommitId cache precisely because it was - // only a partial view of the stream history after reopening from arbitrary revisions. - // Using the in-memory store here keeps the test focused on the public contract that - // matters to callers: duplicate commit identifiers are rejected by the persistence - // boundary even when the current stream instance did not load the conflicting commit. - _store = Wireup.Init().UsingInMemoryPersistence().Build(); - return Task.CompletedTask; - } - - protected override void Cleanup() - { - _stream?.Dispose(); - _store?.Dispose(); - } - - protected async Task SeedCommitAsync(Guid commitId, string eventBody) - { - using var stream = await Store.OpenStreamAsync(StreamId, 0, int.MaxValue, CancellationToken.None).ConfigureAwait(false); - stream.Add(new EventMessage { Body = eventBody }); - await stream.CommitChangesAsync(commitId, CancellationToken.None).ConfigureAwait(false); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/OptimisticEventStreamDuplicateCommitTests.cs b/src/NEventStore.Tests/OptimisticEventStreamDuplicateCommitTests.cs deleted file mode 100644 index 411edf9ce..000000000 --- a/src/NEventStore.Tests/OptimisticEventStreamDuplicateCommitTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore -{ -#if MSTEST - [TestClass] -#endif - public class when_reusing_a_commit_identifier_after_reopening_a_stream : using_an_in_memory_store - { - private readonly Guid _duplicateCommitId = Guid.NewGuid(); - private Exception? _thrown; - - protected override void Context() - { - base.Context(); - SeedCommit(_duplicateCommitId, "already committed"); - - Stream = Store.OpenStream(StreamId, 0, int.MaxValue); - Stream.Add(new EventMessage { Body = "duplicate attempt" }); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Stream.CommitChanges(_duplicateCommitId)); - } - - [Fact] - public void should_throw_a_DuplicateCommitException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reusing_a_commit_identifier_from_history_that_was_not_loaded : using_an_in_memory_store - { - private readonly Guid _duplicateCommitId = Guid.NewGuid(); - private Exception? _thrown; - - protected override void Context() - { - base.Context(); - SeedCommit(_duplicateCommitId, "first event"); - SeedCommit(Guid.NewGuid(), "second event"); - - // This stream is reopened from revision 2, so it sees the correct head revision and - // commit sequence but does not materialize the first commit at all. The duplicate check - // must therefore come from persistence, because a stream-local cache cannot observe the - // earlier commit identifier in this shape of read. - Stream = Store.OpenStream(StreamId, 2, int.MaxValue); - Stream.Add(new EventMessage { Body = "duplicate attempt" }); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Stream.CommitChanges(_duplicateCommitId)); - } - - [Fact] - public void should_throw_a_DuplicateCommitException() - { - _thrown.Should().BeOfType(); - } - } - - public abstract class using_an_in_memory_store : SpecificationBase - { - private IStoreEvents? _store; - private IEventStream? _stream; - - protected string StreamId { get; } = Guid.NewGuid().ToString(); - - protected IStoreEvents Store => _store!; - - protected IEventStream Stream - { - get { return _stream!; } - set { _stream = value; } - } - - protected override void Context() - { - // These duplicate-commit scenarios must go through a real persistence implementation. - // The production change removes the stream-local CommitId cache precisely because it was - // only a partial view of the stream history after reopening from arbitrary revisions. - // Using the in-memory store here keeps the test focused on the public contract that - // matters to callers: duplicate commit identifiers are rejected by the persistence - // boundary even when the current stream instance did not load the conflicting commit. - _store = Wireup.Init().UsingInMemoryPersistence().Build(); - } - - protected override void Cleanup() - { - _stream?.Dispose(); - _store?.Dispose(); - } - - protected void SeedCommit(Guid commitId, string eventBody) - { - using var stream = Store.OpenStream(StreamId, 0, int.MaxValue); - stream.Add(new EventMessage { Body = eventBody }); - stream.CommitChanges(commitId); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/OptimisticEventStreamTests.Async.cs b/src/NEventStore.Tests/OptimisticEventStreamTests.Async.cs deleted file mode 100644 index eec9263bc..000000000 --- a/src/NEventStore.Tests/OptimisticEventStreamTests.Async.cs +++ /dev/null @@ -1,1101 +0,0 @@ -using FakeItEasy; -using FluentAssertions; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable 169 // ReSharper enable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore.Async -{ -#if MSTEST - [TestClass] -#endif - public class when_building_a_stream : on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 7; - private readonly int _eachCommitHas = 2; // events - private ICommit[]? _committed; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eachCommitHas), // 1-2 - BuildCommitStub(2, 4, 2, _eachCommitHas), // 3-4 - BuildCommitStub(3, 6, 3, _eachCommitHas), // 5-6 - BuildCommitStub(4, 8, 4, _eachCommitHas) // 7-8 - ]; - - _committed[0].Headers["Common"] = string.Empty; - _committed[1].Headers["Common"] = string.Empty; - _committed[2].Headers["Common"] = string.Empty; - _committed[3].Headers["Common"] = string.Empty; - _committed[0].Headers["Unique"] = string.Empty; - - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - return Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None); - } - - [Fact] - public void should_have_the_correct_stream_identifier() - { - Stream.StreamId.Should().Be(StreamId); - } - - [Fact] - public void should_have_the_correct_head_stream_revision() - { - Stream.StreamRevision.Should().Be(MaxRevision); - } - - [Fact] - public void should_have_the_correct_head_commit_sequence() - { - Stream.CommitSequence.Should().Be(_committed!.Last().CommitSequence); - } - - [Fact] - public void should_not_include_events_below_the_minimum_revision_indicated() - { - Stream.CommittedEvents.First().Should().Be(_committed![0].Events.Last()); - } - - [Fact] - public void should_not_include_events_above_the_maximum_revision_indicated() - { - Stream.CommittedEvents.Last().Should().Be(_committed!.Last().Events.First()); - } - - [Fact] - public void should_have_all_of_the_committed_events_up_to_the_stream_revision_specified() - { - Stream.CommittedEvents.Count.Should().Be(MaxRevision - MinRevision + 1); - } - - [Fact] - public void should_contain_the_headers_from_the_underlying_commits() - { - Stream.CommittedHeaders.Count.Should().Be(2); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_initializing_an_empty_stream : on_the_event_stream - { - protected override void Context() - { - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(static async (string _, string _, int _, int _, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - return Stream.InitializeAsync(0, int.MaxValue, CancellationToken.None); - } - - [Fact] - public void should_leave_the_stream_revision_at_zero() - { - Stream.StreamRevision.Should().Be(0); - } - - [Fact] - public void should_leave_the_commit_sequence_at_zero() - { - Stream.CommitSequence.Should().Be(0); - } - - [Fact] - public void should_not_materialize_any_committed_events() - { - Stream.CommittedEvents.Should().BeEmpty(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_initializing_an_already_initialized_stream: on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 7; - private readonly int _eachCommitHas = 2; // events - private ICommit[]? _committed; - private Exception? _thrownInitializeRevisionRange; - private Exception? _thrownInitializeSnapshot; - - protected override Task ContextAsync() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eachCommitHas), // 1-2 - BuildCommitStub(2, 4, 2, _eachCommitHas), // 3-4 - BuildCommitStub(3, 6, 3, _eachCommitHas), // 5-6 - BuildCommitStub(4, 8, 4, _eachCommitHas) // 7-8 - ]; - - _committed[0].Headers["Common"] = string.Empty; - _committed[1].Headers["Common"] = string.Empty; - _committed[2].Headers["Common"] = string.Empty; - _committed[3].Headers["Common"] = string.Empty; - _committed[0].Headers["Unique"] = string.Empty; - - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - return Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - _thrownInitializeRevisionRange = await Catch.ExceptionAsync(() => Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None)).ConfigureAwait(false); - _thrownInitializeSnapshot = await Catch.ExceptionAsync(() => Stream.InitializeAsync(new Snapshot(BucketId, StreamId, MinRevision, new object()), MaxRevision, CancellationToken.None)).ConfigureAwait(false); - } - - [Fact] - public void cannot_initialize_a_stream_using_revision_range() - { - _thrownInitializeRevisionRange.Should().BeOfType(); - } - - [Fact] - public void cannot_initialize_a_stream_using_snapshot() - { - _thrownInitializeSnapshot.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_initializing_an_already_used_stream_with_revision_range : on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 7; - private Exception? _thrownInitializeRevisionRange; - private Exception? _thrownInitializeSnapshot; - - protected override void Context() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Add(new EventMessage { Body = "Test" }); - } - - protected override async Task BecauseAsync() - { - _thrownInitializeRevisionRange = await Catch.ExceptionAsync(() => Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None)).ConfigureAwait(false); - _thrownInitializeSnapshot = await Catch.ExceptionAsync(() => Stream.InitializeAsync(new Snapshot(BucketId, StreamId, MinRevision, new object()), MaxRevision, CancellationToken.None)).ConfigureAwait(false); - } - - [Fact] - public void cannot_initialize_a_stream_using_revision_range() - { - _thrownInitializeRevisionRange.Should().BeOfType(); - } - - [Fact] - public void cannot_initialize_a_stream_using_snapshot() - { - _thrownInitializeSnapshot.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_the_head_event_revision_is_less_than_the_max_desired_revision : on_the_event_stream - { - private readonly int _eventsPerCommit = 2; - private ICommit[]? _committed; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 - BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 - BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 - BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 - ]; - - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - return Stream.InitializeAsync(0, int.MaxValue, CancellationToken.None); - } - - [Fact] - public void should_set_the_stream_revision_to_the_revision_of_the_most_recent_event() - { - Stream.StreamRevision.Should().Be(_committed!.Last().StreamRevision); - } - - [Fact] - public void should_set_the_commit_sequence_the_most_recent_commit_sequence() - { - Stream.CommitSequence.Should().Be(_committed!.Last().CommitSequence); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_up_to_revision : on_the_event_stream - { - private readonly int _eventsPerCommit = 2; - private ICommit[]? _committed; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 - BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 - BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 - BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 - ]; - - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, 0, 6, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - return Stream.InitializeAsync(0, 6, CancellationToken.None); - } - - [Fact] - public void should_set_the_stream_revision_to_the_revision_of_the_correct_commit() - { - Stream.StreamRevision.Should().Be(_committed![2].StreamRevision); - } - - [Fact] - public void should_set_the_commit_sequence_to_the_sequence_of_the_correct_commit() - { - Stream.CommitSequence.Should().Be(_committed![2].CommitSequence); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_null_event_message : on_the_event_stream - { - private Exception? _thrown; - - protected override void Because() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _thrown = Catch.Exception(() => Stream.Add(null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void should_throw() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_an_unpopulated_event_message : on_the_event_stream - { - private Exception? _thrown; - - protected override void Because() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _thrown = Catch.Exception(() => Stream.Add(new EventMessage { Body = null })); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void should_throw() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_fully_populated_event_message : on_the_event_stream - { - protected override void Because() - { - Stream.Add(new EventMessage { Body = "populated" }); - } - - [Fact] - public void should_add_the_event_to_the_set_of_uncommitted_events() - { - Stream.UncommittedEvents.Count.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_multiple_populated_event_messages : on_the_event_stream - { - protected override void Because() - { - Stream.Add(new EventMessage { Body = "populated" }); - Stream.Add(new EventMessage { Body = "also populated" }); - } - - [Fact] - public void should_add_all_of_the_events_provided_to_the_set_of_uncommitted_events() - { - Stream.UncommittedEvents.Count.Should().Be(2); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_simple_object_as_an_event_message : on_the_event_stream - { - private const string MyEvent = "some event data"; - - protected override void Because() - { - Stream.Add(new EventMessage { Body = MyEvent }); - } - - [Fact] - public void should_add_the_uncommitted_event_to_the_set_of_uncommitted_events() - { - Stream.UncommittedEvents.Count.Should().Be(1); - } - - [Fact] - public void should_wrap_the_uncommitted_event_in_an_EventMessage_object() - { - Stream.UncommittedEvents.First().Body.Should().Be(MyEvent); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_clearing_any_uncommitted_changes : on_the_event_stream - { - protected override void Context() - { - Stream.Add(new EventMessage { Body = string.Empty }); - } - - protected override void Because() - { - Stream.ClearChanges(); - } - - [Fact] - public void should_clear_all_uncommitted_events() - { - Stream.UncommittedEvents.Count.Should().Be(0); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_an_empty_changeset : on_the_event_stream - { - protected override Task BecauseAsync() - { - return Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None); - } - - [Fact] - public void should_not_call_the_underlying_infrastructure() - { - A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_increment_the_current_stream_revision() - { - Stream.StreamRevision.Should().Be(0); - } - - [Fact] - public void should_not_increment_the_current_commit_sequence() - { - Stream.CommitSequence.Should().Be(0); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_any_uncommitted_changes : on_the_event_stream - { - private readonly Guid _commitId = Guid.NewGuid(); - private readonly Dictionary _headers = new() { { "key", "value" } }; - private readonly EventMessage _uncommitted = new() { Body = string.Empty }; - private CommitAttempt? _constructed; - - protected override void Context() - { - A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)) - .Invokes((CommitAttempt commit, CancellationToken _) => _constructed = commit) - .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => new Commit( - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events)); - _uncommitted.Headers["event-key"] = "event-value"; - Stream.Add(_uncommitted); - foreach (var item in _headers) - { - Stream.UncommittedHeaders[item.Key] = item.Value; - } - } - - protected override Task BecauseAsync() - { - return Stream.CommitChangesAsync(_commitId, CancellationToken.None); - } - - [Fact] - public void should_provide_a_commit_to_the_underlying_infrastructure() - { - A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_build_the_commit_with_the_correct_bucket_identifier() - { - _constructed!.BucketId.Should().Be(BucketId); - } - - [Fact] - public void should_build_the_commit_with_the_correct_stream_identifier() - { - _constructed!.StreamId.Should().Be(StreamId); - } - - [Fact] - public void should_build_the_commit_with_the_correct_stream_revision() - { - _constructed!.StreamRevision.Should().Be(DefaultStreamRevision); - } - - [Fact] - public void should_build_the_commit_with_the_correct_commit_identifier() - { - _constructed!.CommitId.Should().Be(_commitId); - } - - [Fact] - public void should_build_the_commit_with_an_incremented_commit_sequence() - { - _constructed!.CommitSequence.Should().Be(DefaultCommitSequence); - } - - [Fact] - public void should_build_the_commit_with_the_correct_commit_stamp() - { - SystemTime.UtcNow.Should().Be(_constructed!.CommitStamp); - } - - [Fact] - public void should_build_the_commit_with_the_headers_provided() - { - _constructed!.Headers[_headers.First().Key].Should().Be(_headers.First().Value); - } - - [Fact] - public void should_build_the_commit_containing_all_uncommitted_events() - { - _constructed!.Events.Count.Should().Be(_headers.Count); - } - - [Fact] - public void should_build_the_commit_using_the_event_messages_provided() - { - _constructed!.Events.First().Should().Be(_uncommitted); - } - - [Fact] - public void should_preserve_event_message_headers_on_the_commit() - { - _constructed!.Events.First().Headers["event-key"].Should().Be("event-value"); - } - - [Fact] - public void should_contain_a_copy_of_the_headers_provided() - { - _constructed!.Headers.Should().NotBeEmpty(); - _constructed.Headers.Should().NotBeSameAs(Stream.UncommittedHeaders); - _constructed.Headers.Count.Should().Be(_headers.Count); - } - - [Fact] - public void should_update_the_stream_revision() - { - Stream.StreamRevision.Should().Be(_constructed!.StreamRevision); - } - - [Fact] - public void should_update_the_commit_sequence() - { - Stream.CommitSequence.Should().Be(_constructed!.CommitSequence); - } - - [Fact] - public void should_add_the_uncommitted_events_the_committed_events() - { - Stream.CommittedEvents.Last().Should().Be(_uncommitted); - } - - [Fact] - public void should_keep_event_message_headers_after_committing_to_the_stream() - { - Stream.CommittedEvents.Last().Headers["event-key"].Should().Be("event-value"); - } - - [Fact] - public void should_clear_the_uncommitted_events_on_the_stream() - { - Stream.UncommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_clear_the_uncommitted_headers_on_the_stream() - { - Stream.UncommittedHeaders.Should().BeEmpty(); - } - - [Fact] - public void should_copy_the_uncommitted_headers_to_the_committed_stream_headers() - { - Stream.CommittedHeaders.Count.Should().Be(_headers.Count); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_multiple_uncommitted_changes : on_the_event_stream - { - private readonly Guid _commitId = Guid.NewGuid(); - private readonly EventMessage _firstEvent = new() { Body = "first" }; - private readonly EventMessage _secondEvent = new() { Body = "second" }; - private CommitAttempt? _constructed; - - protected override void Context() - { - A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)) - .Invokes((CommitAttempt attempt, CancellationToken _) => _constructed = attempt) - .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => new Commit( - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events)); - - Stream.Add(_firstEvent); - Stream.Add(_secondEvent); - } - - protected override Task BecauseAsync() - { - return Stream.CommitChangesAsync(_commitId, CancellationToken.None); - } - - [Fact] - public void should_build_the_commit_with_all_uncommitted_events_in_order() - { - _constructed!.Events.Should().ContainInOrder(_firstEvent, _secondEvent); - } - - [Fact] - public void should_move_all_committed_events_to_the_stream_in_order() - { - Stream.CommittedEvents.Should().ContainInOrder(_firstEvent, _secondEvent); - } - - [Fact] - public void should_advance_the_stream_revision_by_the_number_of_committed_events() - { - Stream.StreamRevision.Should().Be(2); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_after_another_thread_or_process_has_moved_the_stream_head : on_the_event_stream - { - private const int StreamRevision = 1; - private readonly EventMessage _uncommitted = new() { Body = string.Empty }; - private ICommit[]? _committed; - private ICommit[]? _discoveredOnCommit; - private Exception? _thrown; - - protected override async Task ContextAsync() - { - _committed = [BuildCommitStub(1, 1, 1, 1)]; - _discoveredOnCommit = [BuildCommitStub(2, 3, 2, 2)]; - - A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)).Throws(new ConcurrencyException()); - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, StreamRevision, int.MaxValue, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, StreamRevision +1, int.MaxValue, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _discoveredOnCommit) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - await Stream.InitializeAsync(StreamRevision, int.MaxValue, CancellationToken.None); - Stream.Add(_uncommitted); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - - [Fact] - public void should_query_the_underlying_storage_to_discover_the_new_commits() - { - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, StreamRevision + 1, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_update_the_stream_revision_accordingly() - { - Stream.StreamRevision.Should().Be(_discoveredOnCommit![0].StreamRevision); - } - - [Fact] - public void should_update_the_commit_sequence_accordingly() - { - Stream.CommitSequence.Should().Be(_discoveredOnCommit![0].CommitSequence); - } - - [Fact] - public void should_add_the_newly_discovered_committed_events_to_the_set_of_committed_events_accordingly() - { - Stream.CommittedEvents.Count.Should().Be(_discoveredOnCommit![0].Events.Count + 1); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_invoke_behavior_on_a_disposed_stream : on_the_event_stream - { - private Exception? _thrown; - - protected override void Context() - { - Stream.Dispose(); - } - - protected override async Task BecauseAsync() - { - _thrown = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); - } - - [Fact] - public void should_throw_a_ObjectDisposedException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_modify_the_event_collections : on_the_event_stream - { - [Fact] - public void should_throw_an_exception_when_adding_to_the_committed_collection() - { - Catch.Exception(() => Stream.CommittedEvents.Add(new EventMessage())).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_adding_to_the_uncommitted_collection() - { - Catch.Exception(() => Stream.UncommittedEvents.Add(new EventMessage())).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_clearing_the_committed_collection() - { - Catch.Exception(() => Stream.CommittedEvents.Clear()).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_clearing_the_uncommitted_collection() - { - Catch.Exception(() => Stream.UncommittedEvents.Clear()).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_removing_from_the_committed_collection() - { - Catch.Exception(() => Stream.CommittedEvents.Remove(new EventMessage())).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_removing_from_the_uncommitted_collection() - { - Catch.Exception(() => Stream.UncommittedEvents.Remove(new EventMessage())).Should().BeOfType(); - } - } - - /// - /// All other cases should be handled by persistence providers that have unique indexes on - /// the CommitSequence (the providers should be able to detect that the last commit loaded, after - /// which we are appending new events, is not the last one of the stream) - /// https://github.com/NEventStore/NEventStore/issues/420 - /// -#if MSTEST - [TestClass] -#endif - public class issue_420_when_adding_events_in_the_middle_of_the_last_commit : on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 7; - private readonly int _eventsPerCommit = 2; - private ICommit[]? _committed; - private Exception? _thrown1; - private Exception? _thrown2; - - protected override Task ContextAsync() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 - BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 - BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 - BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 - ]; - - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)) - .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => new Commit( - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events)); - - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - return Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - Stream.Add(new EventMessage() { Body = "Test" }); - // this should fail and cause the stream to be reloaded - _thrown1 = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); - // this should succeed, events will be appended at the end - _thrown2 = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); - } - - [Fact] - public void should_throw_ConcurrencyException() - { - _thrown1.Should().BeOfType(); - } - - public void second_attempt_should_succeed_stream_was_refreshed() - { - _thrown2.Should().BeNull(); - } - - [Fact] - public void events_will_be_appended_to_the_stream_when_committed_on_second_attempt() - { - Stream.CommittedEvents.Count.Should().Be(7); - Stream.CommittedEvents.Last().Body.Should().Be("Test"); - } - } - - /// - /// All other cases should be handled by persistence providers that have unique indexes on - /// the CommitSequence (the providers should be able to detect that the last commit loaded, after - /// which we are appending new events, is not the last one of the stream) - /// https://github.com/NEventStore/NEventStore/issues/420 - /// -#if MSTEST - [TestClass] -#endif - public class issue_420_when_adding_events_in_the_middle_a_commit_if_persistence_returns_too_many_commits : on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 6; - private readonly int _eventsPerCommit = 2; - private ICommit[]? _committed; - private Exception? _thrown1; - private Exception? _thrown2; - - protected override Task ContextAsync() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 - BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 - BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 <-- asked up to this one - BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 - ]; - - // the persistence returns all the data in the stream - A.CallTo(() => PersistenceAsync.GetFromAsync(BucketId, StreamId, MinRevision, MaxRevision, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - foreach (var _commit in _committed) - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - } - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - A.CallTo(() => PersistenceAsync.CommitAsync(A._, CancellationToken.None)) - .ReturnsLazily((CommitAttempt attempt, CancellationToken _) => new Commit( - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events)); - - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - return Stream.InitializeAsync(MinRevision, MaxRevision, CancellationToken.None); - } - - protected override async Task BecauseAsync() - { - Stream.Add(new EventMessage() { Body = "Test" }); - // this should fail and cause the stream to be reloaded - _thrown1 = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); - // this should succeed, events will be appended at the end - _thrown2 = await Catch.ExceptionAsync(() => Stream.CommitChangesAsync(Guid.NewGuid(), CancellationToken.None)).ConfigureAwait(false); - } - - [Fact] - public void first_attempt_should_throw_ConcurrencyException() - { - _thrown1.Should().BeOfType(); - } - - [Fact] - public void second_attempt_should_succeed_stream_was_refreshed() - { - _thrown2.Should().BeNull(); - } - - [Fact] - public void events_will_be_appended_to_the_stream_when_committed_on_second_attempt() - { - Stream.CommittedEvents.Count.Should().Be(6); - Stream.CommittedEvents.Last().Body.Should().Be("Test"); - } - } - - public abstract class on_the_event_stream : SpecificationBase -#if XUNIT - , IUseFixture -#endif - { - protected const int DefaultStreamRevision = 1; - protected const int DefaultCommitSequence = 1; - private ICommitEvents? _persistence; - private ICommitEventsAsync? _persistenceAsync; - private OptimisticEventStream? _stream; - protected const string BucketId = "bucket"; - protected readonly string StreamId = Guid.NewGuid().ToString(); - -#if MSTEST - // todo: we have a problem with ClassInitialize and inheritance, they are not called - // a possible workaround is to use the appdomain unload: https://vijayvepa.wordpress.com/2011/06/16/test-classinitialize-and-classcleanup-inheritance/ - // but each test is run in isolation in MSTest - [ClassInitialize] - public static void ClassInitialize(TestContext context) - { - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - } - - [ClassCleanup] - public static void ClassCleanup() - { - SystemTime.Resolver = null; - } - - public on_the_event_stream() - { - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - } - - protected override void Cleanup() - { - SystemTime.Resolver = null; - } -#endif - -#if NUNIT - // can also consider using the NUnit test attributes instead of the constructor - - protected on_the_event_stream() - { - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - } - - protected override void Cleanup() - { - SystemTime.Resolver = null; - } - -#endif - - protected ICommitEvents Persistence - { - get { return _persistence ??= A.Fake(); } - } - protected ICommitEventsAsync PersistenceAsync - { - get { return _persistenceAsync ??= A.Fake(); } - } - - protected OptimisticEventStream Stream - { - get { return _stream ??= new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); } - set { _stream = value; } - } - - protected ICommit BuildCommitStub(long checkpointToken, int revision, int sequence, int eventCount) - { - var events = new List(eventCount); - for (int i = 0; i < eventCount; i++) - { - events.Add(new EventMessage() { Body = "Body " + (revision - eventCount + i + 1) }); - } - - return new Commit(Bucket.Default, StreamId, revision, Guid.NewGuid(), sequence, SystemTime.UtcNow, checkpointToken, null, events); - } - } - - public class FakeTimeFixture : IDisposable - { - public FakeTimeFixture() - { - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - } - - public void Dispose() - { - SystemTime.Resolver = null; - GC.SuppressFinalize(this); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles -#pragma warning restore 169 // ReSharper enable InconsistentNaming diff --git a/src/NEventStore.Tests/OptimisticEventStreamTests.cs b/src/NEventStore.Tests/OptimisticEventStreamTests.cs deleted file mode 100644 index 937bc87b8..000000000 --- a/src/NEventStore.Tests/OptimisticEventStreamTests.cs +++ /dev/null @@ -1,1111 +0,0 @@ -using FakeItEasy; -using FluentAssertions; -using System.Collections; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable 169 // ReSharper enable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore -{ -#if MSTEST - [TestClass] -#endif - public class when_building_a_stream : on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 7; - private readonly int _eachCommitHas = 2; // events - private ICommit[]? _committed; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eachCommitHas), // 1-2 - BuildCommitStub(2, 4, 2, _eachCommitHas), // 3-4 - BuildCommitStub(3, 6, 3, _eachCommitHas), // 5-6 - BuildCommitStub(4, 8, 4, _eachCommitHas) // 7-8 - ]; - - _committed[0].Headers["Common"] = string.Empty; - _committed[1].Headers["Common"] = string.Empty; - _committed[2].Headers["Common"] = string.Empty; - _committed[3].Headers["Common"] = string.Empty; - _committed[0].Headers["Unique"] = string.Empty; - - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, MinRevision, MaxRevision)).Returns(_committed); - } - - protected override void Because() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Initialize(MinRevision, MaxRevision); - } - - [Fact] - public void should_have_the_correct_stream_identifier() - { - Stream.StreamId.Should().Be(StreamId); - } - - [Fact] - public void should_have_the_correct_head_stream_revision() - { - Stream.StreamRevision.Should().Be(MaxRevision); - } - - [Fact] - public void should_have_the_correct_head_commit_sequence() - { - Stream.CommitSequence.Should().Be(_committed!.Last().CommitSequence); - } - - [Fact] - public void should_not_include_events_below_the_minimum_revision_indicated() - { - Stream.CommittedEvents.First().Should().Be(_committed![0].Events.Last()); - } - - [Fact] - public void should_not_include_events_above_the_maximum_revision_indicated() - { - Stream.CommittedEvents.Last().Should().Be(_committed!.Last().Events.First()); - } - - [Fact] - public void should_have_all_of_the_committed_events_up_to_the_stream_revision_specified() - { - Stream.CommittedEvents.Count.Should().Be(MaxRevision - MinRevision + 1); - } - - [Fact] - public void should_contain_the_headers_from_the_underlying_commits() - { - Stream.CommittedHeaders.Count.Should().Be(2); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_initializing_an_empty_stream : on_the_event_stream - { - protected override void Context() - { - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, 0, int.MaxValue)).Returns([]); - } - - protected override void Because() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Initialize(0, int.MaxValue); - } - - [Fact] - public void should_leave_the_stream_revision_at_zero() - { - Stream.StreamRevision.Should().Be(0); - } - - [Fact] - public void should_leave_the_commit_sequence_at_zero() - { - Stream.CommitSequence.Should().Be(0); - } - - [Fact] - public void should_not_materialize_any_committed_events() - { - Stream.CommittedEvents.Should().BeEmpty(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_initializing_an_already_initialized_stream: on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 7; - private readonly int _eachCommitHas = 2; // events - private ICommit[]? _committed; - private Exception? _thrownInitializeRevisionRange; - private Exception? _thrownInitializeSnapshot; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eachCommitHas), // 1-2 - BuildCommitStub(2, 4, 2, _eachCommitHas), // 3-4 - BuildCommitStub(3, 6, 3, _eachCommitHas), // 5-6 - BuildCommitStub(4, 8, 4, _eachCommitHas) // 7-8 - ]; - - _committed[0].Headers["Common"] = string.Empty; - _committed[1].Headers["Common"] = string.Empty; - _committed[2].Headers["Common"] = string.Empty; - _committed[3].Headers["Common"] = string.Empty; - _committed[0].Headers["Unique"] = string.Empty; - - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, MinRevision, MaxRevision)).Returns(_committed); - - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Initialize(MinRevision, MaxRevision); - } - - protected override void Because() - { - _thrownInitializeRevisionRange = Catch.Exception(() => Stream.Initialize(MinRevision, MaxRevision)); - _thrownInitializeSnapshot = Catch.Exception(() => Stream.Initialize(new Snapshot(BucketId, StreamId, MinRevision, new object()), MaxRevision)); - } - - [Fact] - public void cannot_initialize_a_stream_using_revision_range() - { - _thrownInitializeRevisionRange.Should().BeOfType(); - } - - [Fact] - public void cannot_initialize_a_stream_using_snapshot() - { - _thrownInitializeSnapshot.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_initializing_an_already_used_stream_with_revision_range : on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 7; - private Exception? _thrownInitializeRevisionRange; - private Exception? _thrownInitializeSnapshot; - - protected override void Context() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Add(new EventMessage { Body = "Test" }); - } - - protected override void Because() - { - _thrownInitializeRevisionRange = Catch.Exception(() => Stream.Initialize(MinRevision, MaxRevision)); - _thrownInitializeSnapshot = Catch.Exception(() => Stream.Initialize(new Snapshot(BucketId, StreamId, MinRevision, new object()), MaxRevision)); - } - - [Fact] - public void cannot_initialize_a_stream_using_revision_range() - { - _thrownInitializeRevisionRange.Should().BeOfType(); - } - - [Fact] - public void cannot_initialize_a_stream_using_snapshot() - { - _thrownInitializeSnapshot.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_the_head_event_revision_is_less_than_the_max_desired_revision : on_the_event_stream - { - private readonly int _eventsPerCommit = 2; - private ICommit[]? _committed; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 - BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 - BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 - BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 - ]; - - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, 0, int.MaxValue)).Returns(_committed); - } - - protected override void Because() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Initialize(0, int.MaxValue); - } - - [Fact] - public void should_set_the_stream_revision_to_the_revision_of_the_most_recent_event() - { - Stream.StreamRevision.Should().Be(_committed!.Last().StreamRevision); - } - - [Fact] - public void should_set_the_commit_sequence_the_most_recent_commit_sequence() - { - Stream.CommitSequence.Should().Be(_committed!.Last().CommitSequence); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_up_to_revision : on_the_event_stream - { - private readonly int _eventsPerCommit = 2; - private ICommit[]? _committed; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 - BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 - BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 - BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 - ]; - - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, 0, 6)).Returns(_committed); - } - - protected override void Because() - { - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Initialize(0, 6); - } - - [Fact] - public void should_set_the_stream_revision_to_the_revision_of_the_correct_commit() - { - Stream.StreamRevision.Should().Be(_committed![2].StreamRevision); - } - - [Fact] - public void should_set_the_commit_sequence_to_the_sequence_of_the_correct_commit() - { - Stream.CommitSequence.Should().Be(_committed![2].CommitSequence); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_null_event_message : on_the_event_stream - { - private Exception? _thrown; - - protected override void Because() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _thrown = Catch.Exception(() => Stream.Add(null)); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void should_throw() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_an_unpopulated_event_message : on_the_event_stream - { - private Exception? _thrown; - - protected override void Because() - { -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - _thrown = Catch.Exception(() => Stream.Add(new EventMessage { Body = null })); -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - [Fact] - public void should_throw() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_fully_populated_event_message : on_the_event_stream - { - protected override void Because() - { - Stream.Add(new EventMessage { Body = "populated" }); - } - - [Fact] - public void should_add_the_event_to_the_set_of_uncommitted_events() - { - Stream.UncommittedEvents.Count.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_multiple_populated_event_messages : on_the_event_stream - { - protected override void Because() - { - Stream.Add(new EventMessage { Body = "populated" }); - Stream.Add(new EventMessage { Body = "also populated" }); - } - - [Fact] - public void should_add_all_of_the_events_provided_to_the_set_of_uncommitted_events() - { - Stream.UncommittedEvents.Count.Should().Be(2); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_simple_object_as_an_event_message : on_the_event_stream - { - private const string MyEvent = "some event data"; - - protected override void Because() - { - Stream.Add(new EventMessage { Body = MyEvent }); - } - - [Fact] - public void should_add_the_uncommitted_event_to_the_set_of_uncommitted_events() - { - Stream.UncommittedEvents.Count.Should().Be(1); - } - - [Fact] - public void should_wrap_the_uncommitted_event_in_an_EventMessage_object() - { - Stream.UncommittedEvents.First().Body.Should().Be(MyEvent); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_clearing_any_uncommitted_changes : on_the_event_stream - { - protected override void Context() - { - Stream.Add(new EventMessage { Body = string.Empty }); - } - - protected override void Because() - { - Stream.ClearChanges(); - } - - [Fact] - public void should_clear_all_uncommitted_events() - { - Stream.UncommittedEvents.Count.Should().Be(0); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_an_empty_changeset : on_the_event_stream - { - protected override void Because() - { - Stream.CommitChanges(Guid.NewGuid()); - } - - [Fact] - public void should_not_call_the_underlying_infrastructure() - { - A.CallTo(() => Persistence.Commit(A._)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_increment_the_current_stream_revision() - { - Stream.StreamRevision.Should().Be(0); - } - - [Fact] - public void should_not_increment_the_current_commit_sequence() - { - Stream.CommitSequence.Should().Be(0); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_any_uncommitted_changes : on_the_event_stream - { - private readonly Guid _commitId = Guid.NewGuid(); - private readonly Dictionary _headers = new() { { "key", "value" } }; - private readonly EventMessage _uncommitted = new() { Body = string.Empty }; - private CommitAttempt? _constructed; - - protected override void Context() - { - A.CallTo(() => Persistence.Commit(A._)) - .Invokes((CommitAttempt _) => _constructed = _) - .ReturnsLazily((CommitAttempt attempt) => new Commit( - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events)); - _uncommitted.Headers["event-key"] = "event-value"; - Stream.Add(_uncommitted); - foreach (var item in _headers) - { - Stream.UncommittedHeaders[item.Key] = item.Value; - } - } - - protected override void Because() - { - Stream.CommitChanges(_commitId); - } - - [Fact] - public void should_provide_a_commit_to_the_underlying_infrastructure() - { - A.CallTo(() => Persistence.Commit(A._)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_build_the_commit_with_the_correct_bucket_identifier() - { - _constructed!.BucketId.Should().Be(BucketId); - } - - [Fact] - public void should_build_the_commit_with_the_correct_stream_identifier() - { - _constructed!.StreamId.Should().Be(StreamId); - } - - [Fact] - public void should_build_the_commit_with_the_correct_stream_revision() - { - _constructed!.StreamRevision.Should().Be(DefaultStreamRevision); - } - - [Fact] - public void should_build_the_commit_with_the_correct_commit_identifier() - { - _constructed!.CommitId.Should().Be(_commitId); - } - - [Fact] - public void should_build_the_commit_with_an_incremented_commit_sequence() - { - _constructed!.CommitSequence.Should().Be(DefaultCommitSequence); - } - - [Fact] - public void should_build_the_commit_with_the_correct_commit_stamp() - { - SystemTime.UtcNow.Should().Be(_constructed!.CommitStamp); - } - - [Fact] - public void should_build_the_commit_with_the_headers_provided() - { - _constructed!.Headers[_headers.First().Key].Should().Be(_headers.First().Value); - } - - [Fact] - public void should_build_the_commit_containing_all_uncommitted_events() - { - _constructed!.Events.Count.Should().Be(_headers.Count); - } - - [Fact] - public void should_build_the_commit_using_the_event_messages_provided() - { - _constructed!.Events.First().Should().Be(_uncommitted); - } - - [Fact] - public void should_preserve_event_message_headers_on_the_commit() - { - _constructed!.Events.First().Headers["event-key"].Should().Be("event-value"); - } - - [Fact] - public void should_contain_a_copy_of_the_headers_provided() - { - _constructed!.Headers.Should().NotBeEmpty(); - _constructed.Headers.Should().NotBeSameAs(Stream.UncommittedHeaders); - _constructed.Headers.Count.Should().Be(_headers.Count); - } - - [Fact] - public void should_update_the_stream_revision() - { - Stream.StreamRevision.Should().Be(_constructed!.StreamRevision); - } - - [Fact] - public void should_update_the_commit_sequence() - { - Stream.CommitSequence.Should().Be(_constructed!.CommitSequence); - } - - [Fact] - public void should_add_the_uncommitted_events_the_committed_events() - { - Stream.CommittedEvents.Last().Should().Be(_uncommitted); - } - - [Fact] - public void should_keep_event_message_headers_after_committing_to_the_stream() - { - Stream.CommittedEvents.Last().Headers["event-key"].Should().Be("event-value"); - } - - [Fact] - public void should_clear_the_uncommitted_events_on_the_stream() - { - Stream.UncommittedEvents.Should().BeEmpty(); - } - - [Fact] - public void should_clear_the_uncommitted_headers_on_the_stream() - { - Stream.UncommittedHeaders.Should().BeEmpty(); - } - - [Fact] - public void should_copy_the_uncommitted_headers_to_the_committed_stream_headers() - { - Stream.CommittedHeaders.Count.Should().Be(_headers.Count); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_multiple_uncommitted_changes : on_the_event_stream - { - private readonly Guid _commitId = Guid.NewGuid(); - private readonly EventMessage _firstEvent = new() { Body = "first" }; - private readonly EventMessage _secondEvent = new() { Body = "second" }; - private CommitAttempt? _constructed; - - protected override void Context() - { - A.CallTo(() => Persistence.Commit(A._)) - .Invokes((CommitAttempt attempt) => _constructed = attempt) - .ReturnsLazily((CommitAttempt attempt) => new Commit( - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events)); - - Stream.Add(_firstEvent); - Stream.Add(_secondEvent); - } - - protected override void Because() - { - Stream.CommitChanges(_commitId); - } - - [Fact] - public void should_build_the_commit_with_all_uncommitted_events_in_order() - { - _constructed!.Events.Should().ContainInOrder(_firstEvent, _secondEvent); - } - - [Fact] - public void should_move_all_committed_events_to_the_stream_in_order() - { - Stream.CommittedEvents.Should().ContainInOrder(_firstEvent, _secondEvent); - } - - [Fact] - public void should_advance_the_stream_revision_by_the_number_of_committed_events() - { - Stream.StreamRevision.Should().Be(2); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_after_another_thread_or_process_has_moved_the_stream_head : on_the_event_stream - { - private const int StreamRevision = 1; - private readonly EventMessage _uncommitted = new() { Body = string.Empty }; - private ICommit[]? _committed; - private ICommit[]? _discoveredOnCommit; - private Exception? _thrown; - - protected override void Context() - { - _committed = [BuildCommitStub(1, 1, 1, 1)]; - _discoveredOnCommit = [BuildCommitStub(2, 3, 2, 2)]; - - A.CallTo(() => Persistence.Commit(A._)).Throws(new ConcurrencyException()); - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, StreamRevision, int.MaxValue)).Returns(_committed); - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, StreamRevision + 1, int.MaxValue)).Returns(_discoveredOnCommit); - - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Initialize(StreamRevision, int.MaxValue); - Stream.Add(_uncommitted); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - - [Fact] - public void should_query_the_underlying_storage_to_discover_the_new_commits() - { - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, StreamRevision + 1, int.MaxValue)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_update_the_stream_revision_accordingly() - { - Stream.StreamRevision.Should().Be(_discoveredOnCommit![0].StreamRevision); - } - - [Fact] - public void should_update_the_commit_sequence_accordingly() - { - Stream.CommitSequence.Should().Be(_discoveredOnCommit![0].CommitSequence); - } - - [Fact] - public void should_add_the_newly_discovered_committed_events_to_the_set_of_committed_events_accordingly() - { - Stream.CommittedEvents.Count.Should().Be(_discoveredOnCommit![0].Events.Count + 1); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_invoke_behavior_on_a_disposed_stream : on_the_event_stream - { - private Exception? _thrown; - - protected override void Context() - { - Stream.Dispose(); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); - } - - [Fact] - public void should_throw_a_ObjectDisposedException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_attempting_to_modify_the_event_collections : on_the_event_stream - { - [Fact] - public void should_throw_an_exception_when_adding_to_the_committed_collection() - { - Catch.Exception(() => Stream.CommittedEvents.Add(new EventMessage())).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_adding_to_the_uncommitted_collection() - { - Catch.Exception(() => Stream.UncommittedEvents.Add(new EventMessage())).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_clearing_the_committed_collection() - { - Catch.Exception(() => Stream.CommittedEvents.Clear()).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_clearing_the_uncommitted_collection() - { - Catch.Exception(() => Stream.UncommittedEvents.Clear()).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_removing_from_the_committed_collection() - { - Catch.Exception(() => Stream.CommittedEvents.Remove(new EventMessage())).Should().BeOfType(); - } - - [Fact] - public void should_throw_an_exception_when_removing_from_the_uncommitted_collection() - { - Catch.Exception(() => Stream.UncommittedEvents.Remove(new EventMessage())).Should().BeOfType(); - } - - [Fact] - public void should_copy_to_a_non_generic_array_without_mutating_the_collection() - { - var eventMessage = new EventMessage { Body = "copy" }; - ICollection collection = BuildUncommittedEventsCollection(eventMessage); - var copied = new object[collection.Count]; - - ((ICollection)collection).CopyTo(copied, 0); - - copied.Should().Equal(eventMessage); - collection.Should().ContainSingle().Which.Should().BeSameAs(eventMessage); - } - - [Fact] - public void should_copy_to_a_typed_array_through_the_generic_collection_contract() - { - var eventMessage = new EventMessage { Body = "generic-copy" }; - ICollection collection = BuildUncommittedEventsCollection(eventMessage); - var copied = new EventMessage[collection.Count]; - - collection.CopyTo(copied, 0); - - copied.Should().Equal(eventMessage); - } - - [Fact] - public void should_copy_to_a_typed_array_through_the_non_generic_collection_contract() - { - var eventMessage = new EventMessage { Body = "non-generic-copy" }; - ICollection collection = BuildUncommittedEventsCollection(eventMessage); - var copied = new EventMessage[collection.Count]; - - ((ICollection)collection).CopyTo(copied, 0); - - copied.Should().Equal(eventMessage); - } - - [Fact] - public void should_respect_the_requested_start_index_when_copying_to_a_non_generic_array() - { - var eventMessage = new EventMessage { Body = "indexed-copy" }; - ICollection collection = BuildUncommittedEventsCollection(eventMessage); - var copied = new object[collection.Count + 1]; - - ((ICollection)collection).CopyTo(copied, 1); - - copied[0].Should().BeNull(); - copied[1].Should().BeSameAs(eventMessage); - } - - [Fact] - public void should_throw_when_the_non_generic_destination_array_is_too_small() - { - ICollection collection = BuildUncommittedEventsCollection(new EventMessage { Body = "too-small" }); - var copied = Array.Empty(); - - Catch.Exception(() => ((ICollection)collection).CopyTo(copied, 0)) - .Should().BeOfType(); - } - - [Fact] - public void should_throw_when_the_non_generic_destination_array_cannot_store_event_messages() - { - ICollection collection = BuildUncommittedEventsCollection(new EventMessage { Body = "wrong-array-type" }); - var copied = new string[collection.Count]; - - Catch.Exception(() => ((ICollection)collection).CopyTo(copied, 0)) - .Should().BeOfType(); - } - - private static ICollection BuildUncommittedEventsCollection(EventMessage eventMessage) - { - var stream = new OptimisticEventStream(BucketId, Guid.NewGuid().ToString(), A.Fake(), A.Fake()); - stream.Add(eventMessage); - return stream.UncommittedEvents; - } - } - - /// - /// All other cases should be handled by persistence providers that have unique indexes on - /// the CommitSequence (the providers should be able to detect that the last commit loaded, after - /// which we are appending new events, is not the last one of the stream) - /// https://github.com/NEventStore/NEventStore/issues/420 - /// -#if MSTEST - [TestClass] -#endif - public class issue_420_when_adding_events_in_the_middle_of_the_last_commit : on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 7; - private readonly int _eventsPerCommit = 2; - private ICommit[]? _committed; - private Exception? _thrown1; - private Exception? _thrown2; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 - BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 - BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 - BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 - ]; - - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, MinRevision, MaxRevision)).Returns(_committed); - A.CallTo(() => Persistence.Commit(A._)) - .ReturnsLazily((CommitAttempt attempt) => new Commit( - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events)); - - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Initialize(MinRevision, MaxRevision); - } - - protected override void Because() - { - Stream.Add(new EventMessage() { Body = "Test" }); - // this should fail and cause the stream to be reloaded - _thrown1 = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); - // this should succeed, events will be appended at the end - _thrown2 = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); - } - - [Fact] - public void should_throw_ConcurrencyException() - { - _thrown1.Should().BeOfType(); - } - - public void second_attempt_should_succeed_stream_was_refreshed() - { - _thrown2.Should().BeNull(); - } - - [Fact] - public void events_will_be_appended_to_the_stream_when_committed_on_second_attempt() - { - Stream.CommittedEvents.Count.Should().Be(7); - Stream.CommittedEvents.Last().Body.Should().Be("Test"); - } - } - - /// - /// All other cases should be handled by persistence providers that have unique indexes on - /// the CommitSequence (the providers should be able to detect that the last commit loaded, after - /// which we are appending new events, is not the last one of the stream) - /// https://github.com/NEventStore/NEventStore/issues/420 - /// -#if MSTEST - [TestClass] -#endif - public class issue_420_when_adding_events_in_the_middle_a_commit_if_persistence_returns_too_many_commits : on_the_event_stream - { - private const int MinRevision = 2; - private const int MaxRevision = 6; - private readonly int _eventsPerCommit = 2; - private ICommit[]? _committed; - private Exception? _thrown1; - private Exception? _thrown2; - - protected override void Context() - { - _committed = - [ - BuildCommitStub(1, 2, 1, _eventsPerCommit), // 1-2 - BuildCommitStub(2, 4, 2, _eventsPerCommit), // 3-4 - BuildCommitStub(3, 6, 3, _eventsPerCommit), // 5-6 <-- asked up to this one - BuildCommitStub(4, 8, 4, _eventsPerCommit) // 7-8 - ]; - - // the persistence returns all the data in the stream - A.CallTo(() => Persistence.GetFrom(BucketId, StreamId, MinRevision, MaxRevision)).Returns(_committed); - A.CallTo(() => Persistence.Commit(A._)) - .ReturnsLazily((CommitAttempt attempt) => new Commit( - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - 1, - attempt.Headers, - attempt.Events)); - - Stream = new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); - Stream.Initialize(MinRevision, MaxRevision); - } - - protected override void Because() - { - Stream.Add(new EventMessage() { Body = "Test" }); - // this should fail and cause the stream to be reloaded - _thrown1 = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); - // this should succeed, events will be appended at the end - _thrown2 = Catch.Exception(() => Stream.CommitChanges(Guid.NewGuid())); - } - - [Fact] - public void first_attempt_should_throw_ConcurrencyException() - { - _thrown1.Should().BeOfType(); - } - - [Fact] - public void second_attempt_should_succeed_stream_was_refreshed() - { - _thrown2.Should().BeNull(); - } - - [Fact] - public void events_will_be_appended_to_the_stream_when_committed_on_second_attempt() - { - Stream.CommittedEvents.Count.Should().Be(6); - Stream.CommittedEvents.Last().Body.Should().Be("Test"); - } - } - - public abstract class on_the_event_stream : SpecificationBase -#if XUNIT - , IUseFixture -#endif - { - protected const int DefaultStreamRevision = 1; - protected const int DefaultCommitSequence = 1; - private ICommitEvents? _persistence; - private ICommitEventsAsync? _persistenceAsync; - private OptimisticEventStream? _stream; - protected const string BucketId = "bucket"; - protected readonly string StreamId = Guid.NewGuid().ToString(); - -#if MSTEST - // todo: we have a problem with ClassInitialize and inheritance, they are not called - // a possible workaround is to use the appdomain unload: https://vijayvepa.wordpress.com/2011/06/16/test-classinitialize-and-classcleanup-inheritance/ - // but each test is run in isolation in MSTest - [ClassInitialize] - public static void ClassInitialize(TestContext context) - { - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - } - - [ClassCleanup] - public static void ClassCleanup() - { - SystemTime.Resolver = null; - } - - public on_the_event_stream() - { - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - } - - protected override void Cleanup() - { - SystemTime.Resolver = null; - } -#endif - -#if NUNIT - // can also consider using the NUnit test attributes instead of the constructor - - protected on_the_event_stream() - { - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - } - - protected override void Cleanup() - { - SystemTime.Resolver = null; - } - -#endif - - protected ICommitEvents Persistence - { - get { return _persistence ??= A.Fake(); } - } - protected ICommitEventsAsync PersistenceAsync - { - get { return _persistenceAsync ??= A.Fake(); } - } - - protected OptimisticEventStream Stream - { - get { return _stream ??= new OptimisticEventStream(BucketId, StreamId, Persistence, PersistenceAsync); } - set { _stream = value; } - } - - protected ICommit BuildCommitStub(long checkpointToken, int revision, int sequence, int eventCount) - { - var events = new List(eventCount); - for (int i = 0; i < eventCount; i++) - { - events.Add(new EventMessage() { Body = "Body " + (revision - eventCount + i + 1) }); - } - - return new Commit(Bucket.Default, StreamId, revision, Guid.NewGuid(), sequence, SystemTime.UtcNow, checkpointToken, null, events); - } - } - - public class FakeTimeFixture : IDisposable - { - public FakeTimeFixture() - { - SystemTime.Resolver = () => new DateTime(2012, 1, 1, 13, 0, 0); - } - - public void Dispose() - { - SystemTime.Resolver = null; - GC.SuppressFinalize(this); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles -#pragma warning restore 169 // ReSharper enable InconsistentNaming diff --git a/src/NEventStore.Tests/OptimisticPipelineHookTests.cs b/src/NEventStore.Tests/OptimisticPipelineHookTests.cs deleted file mode 100644 index 383861bab..000000000 --- a/src/NEventStore.Tests/OptimisticPipelineHookTests.cs +++ /dev/null @@ -1,458 +0,0 @@ - -#pragma warning disable 169 // ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests; -using NEventStore.Persistence.AcceptanceTests.BDD; -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore -{ - public static class OptimisticPipelineHookTests - { -#if MSTEST - [TestClass] -#endif - public class when_committing_with_a_sequence_beyond_the_known_end_of_a_stream : using_commit_hooks - { - private const int HeadStreamRevision = 5; - private const int HeadCommitSequence = 1; - private const int ExpectedNextCommitSequence = HeadCommitSequence + 1; - private const int BeyondEndOfStreamCommitSequence = ExpectedNextCommitSequence + 1; - private ICommit? _alreadyCommitted; - private CommitAttempt? _beyondEndOfStream; - private Exception? _thrown; - - protected override void Context() - { - _alreadyCommitted = BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence); - _beyondEndOfStream = BuildCommitAttemptStub(HeadStreamRevision + 1, BeyondEndOfStreamCommitSequence); - - Hook.PostCommit(_alreadyCommitted); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Hook.PreCommit(_beyondEndOfStream!)); - } - - [Fact] - public void should_throw_a_PersistenceException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_with_a_revision_beyond_the_known_end_of_a_stream : using_commit_hooks - { - private const int HeadCommitSequence = 1; - private const int HeadStreamRevision = 1; - private const int NumberOfEventsBeingCommitted = 1; - private const int ExpectedNextStreamRevision = HeadStreamRevision + 1 + NumberOfEventsBeingCommitted; - private const int BeyondEndOfStreamRevision = ExpectedNextStreamRevision + 1; - private ICommit? _alreadyCommitted; - private CommitAttempt? _beyondEndOfStream; - private Exception? _thrown; - - protected override void Context() - { - _alreadyCommitted = BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence); - _beyondEndOfStream = BuildCommitAttemptStub(BeyondEndOfStreamRevision, HeadCommitSequence + 1); - - Hook.PostCommit(_alreadyCommitted); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Hook.PreCommit(_beyondEndOfStream!)); - } - - [Fact] - public void should_throw_a_PersistenceException() - { - _thrown.Should().BeOfType(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_with_a_sequence_less_or_equal_to_the_most_recent_sequence_for_the_stream : using_commit_hooks - { - private const int HeadStreamRevision = 42; - private const int HeadCommitSequence = 42; - private const int DuplicateCommitSequence = HeadCommitSequence; - private CommitAttempt? Attempt; - private ICommit? Committed; - - private Exception? thrown; - - protected override void Context() - { - Committed = BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence); - Attempt = BuildCommitAttemptStub(HeadStreamRevision + 1, DuplicateCommitSequence); - - Hook.PostCommit(Committed); - } - - protected override void Because() - { - thrown = Catch.Exception(() => Hook.PreCommit(Attempt!)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - thrown.Should().BeOfType(); - } - - [Fact] - public void ConcurrencyException_should_have_good_message() - { - thrown!.Message.Should().Contain(Attempt!.StreamId); - thrown.Message.Should().Contain("CommitSequence [" + Attempt.CommitSequence); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_with_a_revision_less_or_equal_to_than_the_most_recent_revision_read_for_the_stream : using_commit_hooks - { - private const int HeadStreamRevision = 3; - private const int HeadCommitSequence = 2; - private const int DuplicateStreamRevision = HeadStreamRevision; - private ICommit? _committed; - private CommitAttempt? _failedAttempt; - private Exception? _thrown; - - protected override void Context() - { - _committed = BuildCommitStub(1, HeadStreamRevision, HeadCommitSequence); - _failedAttempt = BuildCommitAttemptStub(DuplicateStreamRevision, HeadCommitSequence + 1); - - Hook.PostCommit(_committed); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Hook.PreCommit(_failedAttempt!)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - - [Fact] - public void ConcurrencyException_should_have_good_message() - { - _thrown!.Message.Should().Contain(_failedAttempt!.StreamId); - _thrown.Message.Should().Contain("StreamRevision [" + _failedAttempt.CommitSequence); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_with_a_commit_sequence_less_than_or_equal_to_the_most_recent_commit_for_the_stream : using_commit_hooks - { - private const int DuplicateCommitSequence = 1; - private CommitAttempt? _failedAttempt; - private ICommit? _successfulAttempt; - private Exception? _thrown; - - protected override void Context() - { - _successfulAttempt = BuildCommitStub(1, 1, DuplicateCommitSequence); - _failedAttempt = BuildCommitAttemptStub(2, DuplicateCommitSequence); - - Hook.PostCommit(_successfulAttempt); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Hook.PreCommit(_failedAttempt!)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - - [Fact] - public void ConcurrencyException_should_have_good_message() - { - _thrown!.Message.Should().Contain(_failedAttempt!.StreamId); - _thrown.Message.Should().Contain("CommitSequence [" + _failedAttempt.CommitSequence); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing_with_a_stream_revision_less_than_or_equal_to_the_most_recent_commit_for_the_stream : using_commit_hooks - { - private const int DuplicateStreamRevision = 2; - - private CommitAttempt? _failedAttempt; - private ICommit? _successfulAttempt; - private Exception? _thrown; - - protected override void Context() - { - _successfulAttempt = BuildCommitStub(1, DuplicateStreamRevision, 1); - _failedAttempt = BuildCommitAttemptStub(DuplicateStreamRevision, 2); - - Hook.PostCommit(_successfulAttempt); - } - - protected override void Because() - { - _thrown = Catch.Exception(() => Hook.PreCommit(_failedAttempt!)); - } - - [Fact] - public void should_throw_a_ConcurrencyException() - { - _thrown.Should().BeOfType(); - } - - [Fact] - public void Concurrency_exception_should_have_good_message() - { - _thrown!.Message.Should().Contain(_failedAttempt!.StreamId); - _thrown.Message.Should().Contain(_failedAttempt.StreamRevision.ToString()); - } - } - - public class when_tracking_commits : SpecificationBase - { - private const int MaxStreamsToTrack = 2; - private ICommit[]? _trackedCommitAttempts; - - private OptimisticPipelineHook? _hook; - - protected override void Context() - { - _trackedCommitAttempts = - [ - BuildCommit(1, Guid.NewGuid(), Guid.NewGuid()), - BuildCommit(2, Guid.NewGuid(), Guid.NewGuid()), - BuildCommit(3, Guid.NewGuid(), Guid.NewGuid()) - ]; - - _hook = new OptimisticPipelineHook(MaxStreamsToTrack); - } - - protected override void Because() - { - foreach (var commit in _trackedCommitAttempts!) - { - _hook!.Track(commit); - } - } - - [Fact] - public void should_only_contain_streams_explicitly_tracked() - { - ICommit untracked = BuildCommit(4, Guid.Empty, _trackedCommitAttempts![0].CommitId); - _hook!.Contains(untracked).Should().BeFalse(); - } - - [Fact] - public void should_find_tracked_streams() - { - var lastCommit = _trackedCommitAttempts!.Last(); - ICommit stillTracked = BuildCommit(lastCommit.CheckpointToken, lastCommit.StreamId, lastCommit.CommitId); - _hook!.Contains(stillTracked).Should().BeTrue(); - } - - [Fact] - public void should_only_track_the_specified_number_of_streams() - { - var firstCommit = _trackedCommitAttempts![0]; - ICommit droppedFromTracking = BuildCommit(firstCommit.CheckpointToken, firstCommit.StreamId, firstCommit.CommitId); - _hook!.Contains(droppedFromTracking).Should().BeFalse(); - } - - private static Commit BuildCommit(long checkpointToken, Guid streamId, Guid commitId) - { - return BuildCommit(checkpointToken, streamId.ToString(), commitId); - } - - private static Commit BuildCommit(long checkpointToken, string streamId, Guid commitId) - { - return new Commit(Bucket.Default, streamId, 1, commitId, 1, SystemTime.UtcNow, checkpointToken, null, null); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging : SpecificationBase - { - private ICommit? _trackedCommit; - private OptimisticPipelineHook? _hook; - - protected override void Context() - { - _trackedCommit = BuildCommit(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()); - _hook = new OptimisticPipelineHook(); - _hook.Track(_trackedCommit); - } - - protected override void Because() - { - _hook!.OnPurge(); - } - - [Fact] - public void should_not_track_commit() - { - _hook!.Contains(_trackedCommit!).Should().BeFalse(); - } - - private static Commit BuildCommit(Guid bucketId, Guid streamId, Guid commitId) - { - return new Commit(bucketId.ToString(), streamId.ToString(), 0, commitId, 0, SystemTime.UtcNow, - 1, null, null); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_a_bucket : SpecificationBase - { - private Commit? _trackedCommitBucket1; - private Commit? _trackedCommitBucket2; - private OptimisticPipelineHook? _hook; - - protected override void Context() - { - _trackedCommitBucket1 = BuildCommit(1, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()); - _trackedCommitBucket2 = BuildCommit(2, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid()); - _hook = new OptimisticPipelineHook(); - _hook.Track(_trackedCommitBucket1); - _hook.Track(_trackedCommitBucket2); - } - - protected override void Because() - { - _hook!.OnPurge(_trackedCommitBucket1!.BucketId); - } - - [Fact] - public void should_not_track_the_commit_in_bucket() - { - _hook!.Contains(_trackedCommitBucket1!).Should().BeFalse(); - } - - [Fact] - public void should_track_the_commit_in_other_bucket() - { - _hook!.Contains(_trackedCommitBucket2!).Should().BeTrue(); - } - - private static Commit BuildCommit(long checkpointToken, Guid bucketId, Guid streamId, Guid commitId) - { - return new Commit(bucketId.ToString(), streamId.ToString(), 0, commitId, 0, SystemTime.UtcNow, - checkpointToken, null, null); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_deleting_a_stream : SpecificationBase - { - private Commit? _trackedCommit; - private Commit? _trackedCommitDeleted; - private OptimisticPipelineHook? _hook; - private readonly Guid _bucketId = Guid.NewGuid(); - private readonly Guid _streamIdDeleted = Guid.NewGuid(); - - protected override void Context() - { - _trackedCommit = BuildCommit(1, _bucketId, Guid.NewGuid(), Guid.NewGuid()); - _trackedCommitDeleted = BuildCommit(2, _bucketId, _streamIdDeleted, Guid.NewGuid()); - _hook = new OptimisticPipelineHook(); - _hook.Track(_trackedCommit); - _hook.Track(_trackedCommitDeleted); - } - - protected override void Because() - { - _hook!.OnDeleteStream(_trackedCommitDeleted!.BucketId, _trackedCommitDeleted.StreamId); - } - - [Fact] - public void should_not_track_the_commit_in_the_deleted_stream() - { - _hook!.Contains(_trackedCommitDeleted!).Should().BeFalse(); - } - - [Fact] - public void should_track_the_commit_that_is_not_in_the_deleted_stream() - { - _hook!.Contains(_trackedCommit!).Should().BeTrue(); - } - - private static Commit BuildCommit(long checkpointToken, Guid bucketId, Guid streamId, Guid commitId) - { - return new Commit(bucketId.ToString(), streamId.ToString(), 0, commitId, 0, SystemTime.UtcNow, - checkpointToken, null, null); - } - } - - public abstract class using_commit_hooks : SpecificationBase - { - protected readonly OptimisticPipelineHook Hook = new(); - private readonly string _streamId = Guid.NewGuid().ToString(); - - /* - protected CommitAttempt BuildCommitAttempt(Guid commitId) - { - return new CommitAttempt(_streamId, 1, commitId, 1, SystemTime.UtcNow, null, null); - } - */ - - protected ICommit BuildCommitStub(long checkpointToken, int streamRevision, int commitSequence) - { - List events = new[] { new EventMessage() }.ToList(); - return new Commit(Bucket.Default, _streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, checkpointToken, null, events); - } - - protected CommitAttempt BuildCommitAttemptStub(int streamRevision, int commitSequence) - { - EventMessage[] events = [new EventMessage()]; - return new CommitAttempt(Bucket.Default, _streamId, streamRevision, Guid.NewGuid(), commitSequence, SystemTime.UtcNow, null, events); - } - - protected ICommit BuildCommitStub(long checkpointToken, Guid commitId, int streamRevision, int commitSequence) - { - List events = new[] { new EventMessage() }.ToList(); - return new Commit(Bucket.Default, _streamId, streamRevision, commitId, commitSequence, SystemTime.UtcNow, checkpointToken, null, events); - } - } - } -} - -#pragma warning restore 169 // ReSharper enable InconsistentNaming -#pragma warning restore IDE1006 // Naming Styles \ No newline at end of file diff --git a/src/NEventStore.Tests/Persistence/InMemory/InMemoryPersistenceTests.cs b/src/NEventStore.Tests/Persistence/InMemory/InMemoryPersistenceTests.cs deleted file mode 100644 index 9fd69d50a..000000000 --- a/src/NEventStore.Tests/Persistence/InMemory/InMemoryPersistenceTests.cs +++ /dev/null @@ -1,422 +0,0 @@ -using NEventStore.Persistence.AcceptanceTests.BDD; -#pragma warning disable IDE1006 // Naming Styles - -using FluentAssertions; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -namespace NEventStore.Persistence.InMemory -{ -#if MSTEST - [TestClass] -#endif - public class when_getting_from_to_then_should_not_get_later_commits : SpecificationBase - { - private readonly DateTime _endDate = new(2013, 1, 2); - private readonly DateTime _startDate = new(2013, 1, 1); - private ICommit[]? _commits; - private InMemoryPersistenceEngine? _engine; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - var streamId = Guid.NewGuid().ToString(); - _engine.Commit(new CommitAttempt(streamId, 1, Guid.NewGuid(), 1, _startDate, new Dictionary(), [new EventMessage()])); - _engine.Commit(new CommitAttempt(streamId, 2, Guid.NewGuid(), 2, _endDate, new Dictionary(), [new EventMessage()])); - } - - protected override void Because() - { - _commits = _engine!.GetFromTo(Bucket.Default, _startDate, _endDate).ToArray(); - } - - [Fact] - public void should_return_two_commits() - { - _commits!.Length.Should().Be(1); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_getting_from_to_checkpoint_then_should_not_get_later_commits : SpecificationBase - { - private readonly DateTime _endDate = new(2013, 1, 2); - private readonly DateTime _startDate = new(2013, 1, 1); - private ICommit[]? _commits; - private InMemoryPersistenceEngine? _engine; - private ICommit? _commit1; - private ICommit? _commit2; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - var streamId = Guid.NewGuid().ToString(); - _commit1 = _engine.Commit(new CommitAttempt(streamId, 1, Guid.NewGuid(), 1, _startDate, new Dictionary(), [new EventMessage()])); - _commit2 = _engine.Commit(new CommitAttempt(streamId, 2, Guid.NewGuid(), 2, _endDate, new Dictionary(), [new EventMessage()])); - _engine.Commit(new CommitAttempt(streamId, 3, Guid.NewGuid(), 3, _endDate.AddDays(1), new Dictionary(), [new EventMessage()])); - } - - protected override void Because() - { - _commits = _engine!.GetFromTo(Bucket.Default, _commit1!.CheckpointToken, _commit2!.CheckpointToken).ToArray(); - } - - [Fact] - public void should_return_two_commits() - { - _commits!.Length.Should().Be(1); - _commit2!.Should().Be(_commits![0]); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_getting_bucket_commits_after_a_checkpoint_that_only_exists_in_another_bucket : SpecificationBase - { - private InMemoryPersistenceEngine? _engine; - private ICommit[]? _commits; - private ICommit? _expected; - private long _checkpointFromOtherBucket; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - // This regression proves bucket checkpoint reads are based on the global checkpoint - // position, not on whether the lower-bound checkpoint exists inside the same bucket. - _engine.Commit(new CommitAttempt("b1", "stream-1", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _checkpointFromOtherBucket = _engine.Commit(new CommitAttempt("b2", "stream-2", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()]))!.CheckpointToken; - _expected = _engine.Commit(new CommitAttempt("b1", "stream-1", 2, Guid.NewGuid(), 2, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - } - - protected override void Because() - { - _commits = _engine!.GetFrom("b1", _checkpointFromOtherBucket).ToArray(); - } - - [Fact] - public void should_only_return_bucket_commits_after_the_checkpoint() - { - _commits!.Should().HaveCount(1); - _commits[0].Should().Be(_expected); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_getting_stream_commits_from_a_revision_range : SpecificationBase - { - private InMemoryPersistenceEngine? _engine; - private ICommit[]? _commits; - private ICommit? _first; - private ICommit? _second; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - // The first commit ends before the requested minimum revision and should be skipped, - // while the second overlaps the requested range and must still be returned. - _first = _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 2, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage(), new EventMessage()])); - _second = _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 4, Guid.NewGuid(), 2, DateTime.UtcNow, new Dictionary(), [new EventMessage(), new EventMessage()])); - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-2", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - } - - protected override void Because() - { - _commits = _engine!.GetFrom(Bucket.Default, "stream-1", 3, 4).ToArray(); - } - - [Fact] - public void should_only_return_commits_that_overlap_the_requested_revision_range() - { - _commits!.Should().HaveCount(1); - _commits[0].Should().Be(_second); - _commits.Should().NotContain(_first); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_commit_after_a_snapshot_exists : SpecificationBase - { - private InMemoryPersistenceEngine? _engine; - private IStreamHead? _streamHead; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - // This regression protects the contract that later commits advance HeadRevision without - // erasing the last known SnapshotRevision for the stream. The new dictionary-based head - // index must preserve that same pair of values that the old remove/add linked-list - // implementation produced. - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _engine.AddSnapshot(new Snapshot(Bucket.Default, "stream-1", 1, "snapshot")); - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 2, Guid.NewGuid(), 2, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - } - - protected override void Because() - { - _streamHead = _engine!.GetStreamsToSnapshot(Bucket.Default, 1).SingleOrDefault(x => x.StreamId == "stream-1"); - } - - [Fact] - public void should_keep_the_snapshot_revision_on_the_stream_head() - { - _streamHead!.SnapshotRevision.Should().Be(1); - } - - [Fact] - public void should_advance_the_head_revision_to_the_latest_commit() - { - _streamHead!.HeadRevision.Should().Be(2); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_retrieving_the_latest_snapshot_not_beyond_a_revision : SpecificationBase - { - private InMemoryPersistenceEngine? _engine; - private ISnapshot? _snapshot; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - // Multiple snapshots for the same stream must still resolve to the greatest revision - // that is <= maxRevision. The new per-stream sorted snapshot lists are specifically - // designed to preserve this selection rule without scanning unrelated streams. - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 6, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage(), new EventMessage(), new EventMessage(), new EventMessage(), new EventMessage(), new EventMessage()])); - _engine.AddSnapshot(new Snapshot(Bucket.Default, "stream-1", 1, "rev1")); - _engine.AddSnapshot(new Snapshot(Bucket.Default, "stream-1", 3, "rev3")); - _engine.AddSnapshot(new Snapshot(Bucket.Default, "stream-1", 5, "rev5")); - _engine.Commit(new CommitAttempt(Bucket.Default, "other-stream", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _engine.AddSnapshot(new Snapshot(Bucket.Default, "other-stream", 1, "other")); - } - - protected override void Because() - { - _snapshot = _engine!.GetSnapshot(Bucket.Default, "stream-1", 4); - } - - [Fact] - public void should_return_the_most_recent_snapshot_not_beyond_the_requested_revision() - { - _snapshot!.StreamRevision.Should().Be(3); - } - - [Fact] - public void should_return_the_payload_of_that_snapshot() - { - _snapshot!.Payload.Should().Be("rev3"); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_adding_a_duplicate_snapshot_revision_for_a_stream : SpecificationBase - { - private InMemoryPersistenceEngine? _engine; - private bool _added; - private ISnapshot? _snapshot; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - // Duplicate snapshot revisions are intentionally ignored instead of replaced. That - // behavior existed before the new snapshot index and must remain stable because callers - // may rely on the first successfully stored snapshot winning for that revision. - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _engine.AddSnapshot(new Snapshot(Bucket.Default, "stream-1", 1, "original")); - } - - protected override void Because() - { - _added = _engine!.AddSnapshot(new Snapshot(Bucket.Default, "stream-1", 1, "duplicate")); - _snapshot = _engine.GetSnapshot(Bucket.Default, "stream-1", 1); - } - - [Fact] - public void should_reject_the_duplicate_snapshot() - { - _added.Should().BeFalse(); - } - - [Fact] - public void should_keep_the_original_snapshot_payload() - { - _snapshot!.Payload.Should().Be("original"); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_deleting_a_stream_with_snapshots_and_stream_head : SpecificationBase - { - private InMemoryPersistenceEngine? _engine; - private ISnapshot? _snapshot; - private IStreamHead? _streamHead; - private ICommit[]? _remainingCommits; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - // Stream deletion must remove the primary commits and every metadata index that points - // at them. If the direct dictionaries are not updated in the same critical section, the - // engine can return stale snapshots or stale stream heads for a deleted stream. - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 2, Guid.NewGuid(), 2, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _engine.AddSnapshot(new Snapshot(Bucket.Default, "stream-1", 1, "snapshot")); - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-2", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - } - - protected override void Because() - { - _engine!.DeleteStream(Bucket.Default, "stream-1"); - _snapshot = _engine.GetSnapshot(Bucket.Default, "stream-1", int.MaxValue); - _streamHead = _engine.GetStreamsToSnapshot(Bucket.Default, 0).SingleOrDefault(x => x.StreamId == "stream-1"); - _remainingCommits = _engine.GetFrom(Bucket.Default, "stream-1", 0, int.MaxValue).ToArray(); - } - - [Fact] - public void should_remove_all_commits_from_the_deleted_stream() - { - _remainingCommits!.Should().BeEmpty(); - } - - [Fact] - public void should_remove_the_snapshot_lookup_entry() - { - _snapshot.Should().BeNull(); - } - - [Fact] - public void should_remove_the_stream_head_lookup_entry() - { - _streamHead.Should().BeNull(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_a_bucket_with_stream_heads_and_snapshots : SpecificationBase - { - private InMemoryPersistenceEngine? _engine; - private ICommit[]? _bucketACommits; - private ICommit[]? _bucketBCommits; - private ISnapshot? _bucketASnapshot; - private ISnapshot? _bucketBSnapshot; - private IStreamHead[]? _bucketAHeads; - private IStreamHead[]? _bucketBHeads; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - // Bucket purge should clear every index only for the targeted bucket. This regression - // protects against sharing state across buckets now that the bucket internals keep more - // direct lookup structures. - _engine.Commit(new CommitAttempt("a", "stream-1", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _engine.AddSnapshot(new Snapshot("a", "stream-1", 1, "snapshot-a")); - _engine.Commit(new CommitAttempt("b", "stream-1", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _engine.AddSnapshot(new Snapshot("b", "stream-1", 1, "snapshot-b")); - } - - protected override void Because() - { - _engine!.Purge("a"); - _bucketACommits = _engine.GetFrom("a", 0).ToArray(); - _bucketBCommits = _engine.GetFrom("b", 0).ToArray(); - _bucketASnapshot = _engine.GetSnapshot("a", "stream-1", int.MaxValue); - _bucketBSnapshot = _engine.GetSnapshot("b", "stream-1", int.MaxValue); - _bucketAHeads = _engine.GetStreamsToSnapshot("a", 0).ToArray(); - _bucketBHeads = _engine.GetStreamsToSnapshot("b", 0).ToArray(); - } - - [Fact] - public void should_clear_commits_snapshots_and_stream_heads_for_the_purged_bucket() - { - _bucketACommits!.Should().BeEmpty(); - _bucketASnapshot.Should().BeNull(); - _bucketAHeads!.Should().BeEmpty(); - } - - [Fact] - public void should_leave_other_buckets_untouched() - { - _bucketBCommits!.Should().HaveCount(1); - _bucketBSnapshot!.Payload.Should().Be("snapshot-b"); - _bucketBHeads!.Should().ContainSingle(x => x.StreamId == "stream-1"); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_the_store_with_stream_heads_and_snapshots : SpecificationBase - { - private InMemoryPersistenceEngine? _engine; - private ICommit[]? _commits; - private ISnapshot? _snapshot; - private IStreamHead[]? _streamHeads; - - protected override void Context() - { - _engine = new InMemoryPersistenceEngine(); - _engine.Initialize(); - - // Store-wide purge must be the final safety net that clears every direct lookup index, - // not just the primary commit collections. Otherwise callers can observe stale metadata - // after a full purge even though no commits remain anywhere in the store. - _engine.Commit(new CommitAttempt(Bucket.Default, "stream-1", 1, Guid.NewGuid(), 1, DateTime.UtcNow, new Dictionary(), [new EventMessage()])); - _engine.AddSnapshot(new Snapshot(Bucket.Default, "stream-1", 1, "snapshot")); - } - - protected override void Because() - { - _engine!.Purge(); - _commits = _engine.GetFrom(0).ToArray(); - _snapshot = _engine.GetSnapshot(Bucket.Default, "stream-1", int.MaxValue); - _streamHeads = _engine.GetStreamsToSnapshot(Bucket.Default, 0).ToArray(); - } - - [Fact] - public void should_clear_all_metadata_and_commit_indexes() - { - _commits!.Should().BeEmpty(); - _snapshot.Should().BeNull(); - _streamHeads!.Should().BeEmpty(); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixture.cs b/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixture.cs deleted file mode 100644 index 15463be7f..000000000 --- a/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixture.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ReSharper disable once CheckNamespace -using NEventStore.Persistence.InMemory; - -namespace NEventStore.Persistence.AcceptanceTests -{ - public partial class PersistenceEngineFixture - { - public PersistenceEngineFixture() - { - _createPersistence = _ => - new InMemoryPersistenceEngine(); - } - } -} diff --git a/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixtureAsync.cs b/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixtureAsync.cs deleted file mode 100644 index 4b3ab88c3..000000000 --- a/src/NEventStore.Tests/Persistence/InMemory/PersistenceEngineFixtureAsync.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ReSharper disable once CheckNamespace -using NEventStore.Persistence.InMemory; - -namespace NEventStore.Persistence.AcceptanceTests.Async -{ - public partial class PersistenceEngineFixtureAsync - { - public PersistenceEngineFixtureAsync() - { - _createPersistence = _ => - new InMemoryPersistenceEngine(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.Async.cs b/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.Async.cs deleted file mode 100644 index 4513d2e81..000000000 --- a/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.Async.cs +++ /dev/null @@ -1,823 +0,0 @@ -using FakeItEasy; -using FluentAssertions; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -using FluentAssertions; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable 169 -// ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore.Async -{ -#if MSTEST - [TestClass] -#endif - public class when_disposing_the_decorator : using_underlying_persistence - { - protected override void Because() - { - Decorator.Dispose(); - } - - [Fact] - public void should_dispose_the_underlying_persistence() - { - A.CallTo(() => persistence.Dispose()).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_in_a_bucket_from_date : using_underlying_persistence - { - private ICommit? _commit; - private DateTime _date; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - - protected override void Context() - { - _date = DateTime.Now; - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, _date, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFrom(Bucket.Default, _date)).Returns([_commit]); - } - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // Forces enumeration of commits. - Decorator.GetFrom(Bucket.Default, _date).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFrom(Bucket.Default, _date)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_in_a_bucket_from_min_to_max_revision : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromAsync(Bucket.Default, _commit.StreamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // Forces enumeration of commits. - var streamObserver = new CommitStreamObserver(); - return Decorator.GetFromAsync(Bucket.Default, _commit!.StreamId, 0, int.MaxValue, streamObserver, CancellationToken.None); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromAsync(Bucket.Default, _commit!.StreamId, 0, int.MaxValue, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_events_in_a_bucket_from_date_to_date : using_underlying_persistence - { - private ICommit? _commit; - private DateTime _end; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private DateTime _start; - - protected override void Context() - { - _start = DateTime.Now; - _end = DateTime.Now; - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).Returns([_commit]); - } - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // Forces enumeration of commits - Decorator.GetFromTo(Bucket.Default, _start, _end).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_events_in_all_buckets_from_checkpoint_to_checkpoint : using_underlying_persistence - { - private ICommit? _commit; - private Int64 _end; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private Int64 _start; - - protected override void Context() - { - _start = 0; - _end = 1; - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromToAsync(_start, _end, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - return Decorator.GetFromToAsync(_start, _end, new CommitStreamObserver(), CancellationToken.None); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromToAsync(_start, _end, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_events_in_a_bucket_from_checkpoint_to_checkpoint : using_underlying_persistence - { - private ICommit? _commit; - private Int64 _end; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private Int64 _start; - - protected override void Context() - { - _start = 0; - _end = 1; - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromToAsync(Bucket.Default, _start, _end, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - return Decorator.GetFromToAsync(Bucket.Default, _start, _end, new CommitStreamObserver(), CancellationToken.None); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromToAsync(Bucket.Default, _start, _end, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing : using_underlying_persistence - { - private CommitAttempt? _attempt; - - protected override void Context() - { - _attempt = new CommitAttempt(streamId, 1, Guid.NewGuid(), 1, DateTime.Now, null, [new EventMessage()]); - } - - protected override Task BecauseAsync() - { - return Decorator.CommitAsync(_attempt!, CancellationToken.None); - } - - [Fact] - public void should_dispose_the_underlying_persistence() - { - A.CallTo(() => persistence.CommitAsync(_attempt!, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_from_checkpoint : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (long checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - return Decorator.GetFromAsync(0, new CommitStreamObserver(), CancellationToken.None); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_in_a_bucket_from_checkpoint : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromAsync(Bucket.Default, 0, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (string bucketId, long checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override Task BecauseAsync() - { - return Decorator.GetFromAsync(Bucket.Default, 0, new CommitStreamObserver(), CancellationToken.None); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromAsync(Bucket.Default, 0, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging : using_underlying_persistence - { - private IPipelineHook? _hook; - private IPipelineHookAsync? _hookAsync; - - protected override void Context() - { - _hook = A.Fake(); - pipelineHooks.Add(_hook); - - _hookAsync = A.Fake(); - pipelineHooksAsync.Add(_hookAsync); - } - - protected override Task BecauseAsync() - { - return Decorator.PurgeAsync(CancellationToken.None); - } - - [Fact] - public void should_call_the_pipeline_hook_purge() - { - A.CallTo(() => _hook!.OnPurge(null)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_call_the_async_pipeline_hook_purge() - { - A.CallTo(() => _hookAsync!.OnPurgeAsync(null, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_a_bucket : using_underlying_persistence - { - private IPipelineHook? _hook; - private IPipelineHookAsync? _hookAsync; - private const string _bucketId = "Bucket"; - - protected override void Context() - { - _hook = A.Fake(); - pipelineHooks.Add(_hook); - - _hookAsync = A.Fake(); - pipelineHooksAsync.Add(_hookAsync); - } - - protected override Task BecauseAsync() - { - return Decorator.PurgeAsync(_bucketId, CancellationToken.None); - } - - [Fact] - public void should_call_the_pipeline_hook_purge() - { - A.CallTo(() => _hook!.OnPurge(_bucketId)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_call_the_async_pipeline_hook_purge() - { - A.CallTo(() => _hookAsync!.OnPurgeAsync(_bucketId, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_deleting_a_stream : using_underlying_persistence - { - private IPipelineHook? _hook; - private IPipelineHookAsync? _hookAsync; - private const string _bucketId = "Bucket"; - private const string _streamId = "Stream"; - - protected override void Context() - { - _hook = A.Fake(); - pipelineHooks.Add(_hook); - - _hookAsync = A.Fake(); - pipelineHooksAsync.Add(_hookAsync); - } - - protected override Task BecauseAsync() - { - return Decorator.DeleteStreamAsync(_bucketId, _streamId, CancellationToken.None); - } - - [Fact] - public void should_call_the_pipeline_hook_purge() - { - A.CallTo(() => _hook!.OnDeleteStream(_bucketId, _streamId)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_call_the_async_pipeline_hook_purge() - { - A.CallTo(() => _hookAsync!.OnDeleteStreamAsync(_bucketId, _streamId, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_from_checkpoint_with_filtering_PipelineHook_Sync : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private IList? _commits; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(null); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (long checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Decorator.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); - _commits = observer.Commits; - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_first_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_not_pass_the_events_through_the_second_pipeline_hooks() - { - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_pass_the_events_through_the_first_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_pass_the_events_through_the_second_async_pipeline_hooks() - { - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); - } - - [Fact] - public void commit_list_should_be_empty() - { - _commits.Should().BeEmpty(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_from_checkpoint_with_filtering_PipelineHook_Async : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private IList? _commits; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(null)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)) - .ReturnsLazily(async (long checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellation) => - { - await asyncObserver.OnNextAsync(_commit, cancellation).ConfigureAwait(false); - await asyncObserver.OnCompletedAsync(cancellation).ConfigureAwait(false); - }); - } - - protected override async Task BecauseAsync() - { - var observer = new CommitStreamObserver(); - await Decorator.GetFromAsync(0, observer, CancellationToken.None).ConfigureAwait(false); - _commits = observer.Commits; - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromAsync(0, A>.Ignored, CancellationToken.None)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_first_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_the_events_through_the_second_pipeline_hooks() - { - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_the_events_through_the_first_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_not_pass_the_events_through_the_second_async_pipeline_hooks() - { - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); - } - - [Fact] - public void commit_list_should_be_empty() - { - _commits.Should().BeEmpty(); - } - } - - public abstract class using_underlying_persistence : SpecificationBase - { - private PipelineHooksAwarePersistStreamsDecorator? decorator; - protected readonly IPersistStreams persistence = A.Fake(); - protected readonly List pipelineHooks = []; - protected readonly List pipelineHooksAsync = []; - protected readonly string streamId = Guid.NewGuid().ToString(); - - public PipelineHooksAwarePersistStreamsDecorator Decorator - { - get { return decorator ??= new PipelineHooksAwarePersistStreamsDecorator(persistence, pipelineHooks.Select(x => x), pipelineHooksAsync.Select(x => x)); } - set { decorator = value; } - } - } -} - -#pragma warning restore IDE1006 // Naming Styles -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.cs b/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.cs deleted file mode 100644 index 5ba601795..000000000 --- a/src/NEventStore.Tests/PipelineHooksAwarePersistanceDecoratorTests.cs +++ /dev/null @@ -1,788 +0,0 @@ -using FakeItEasy; -using FluentAssertions; -using NEventStore.Persistence; -using NEventStore.Persistence.AcceptanceTests.BDD; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -using FluentAssertions; -#endif -#if NUNIT -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable 169 -// ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore -{ -#if MSTEST - [TestClass] -#endif - public class when_disposing_the_decorator : using_underlying_persistence - { - protected override void Because() - { - Decorator.Dispose(); - } - - [Fact] - public void should_dispose_the_underlying_persistence() - { - A.CallTo(() => persistence.Dispose()).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_in_a_bucket_from_date : using_underlying_persistence - { - private ICommit? _commit; - private DateTime _date; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - - protected override void Context() - { - _date = DateTime.Now; - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, _date, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFrom(Bucket.Default, _date)).Returns([_commit]); - } - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // Forces enumeration of commits. - Decorator.GetFrom(Bucket.Default, _date).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFrom(Bucket.Default, _date)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_in_a_bucket_from_min_to_max_revision : using_underlying_persistence - { - private Commit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFrom(Bucket.Default, _commit.StreamId, 0, int.MaxValue)) - .Returns([_commit]); - } - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // Forces enumeration of commits. - Decorator.GetFrom(Bucket.Default, _commit!.StreamId, 0, int.MaxValue).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFrom(Bucket.Default, _commit!.StreamId, 0, int.MaxValue)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_events_in_a_bucket_from_date_to_date : using_underlying_persistence - { - private ICommit? _commit; - private DateTime _end; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private DateTime _start; - - protected override void Context() - { - _start = DateTime.Now; - _end = DateTime.Now; - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).Returns([_commit]); - } - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // Forces enumeration of commits - Decorator.GetFromTo(Bucket.Default, _start, _end).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_events_in_all_buckets_from_checkpoint_to_checkpoint : using_underlying_persistence - { - private ICommit? _commit; - private Int64 _end; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private Int64 _start; - - protected override void Context() - { - _start = 0; - _end = 1; - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromTo(_start, _end)).Returns([_commit]); - } - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // Forces enumeration of commits - Decorator.GetFromTo(_start, _end).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromTo(_start, _end)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_all_events_in_a_bucket_from_checkpoint_to_checkpoint : using_underlying_persistence - { - private ICommit? _commit; - private Int64 _end; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private Int64 _start; - - protected override void Context() - { - _start = 0; - _end = 1; - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).Returns([_commit]); - } - - protected override void Because() - { - // ReSharper disable once ReturnValueOfPureMethodIsNotUsed - // Forces enumeration of commits - Decorator.GetFromTo(Bucket.Default, _start, _end).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFromTo(Bucket.Default, _start, _end)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_committing : using_underlying_persistence - { - private CommitAttempt? _attempt; - - protected override void Context() - { - _attempt = new CommitAttempt(streamId, 1, Guid.NewGuid(), 1, DateTime.Now, null, [new EventMessage()]); - } - - protected override void Because() - { - Decorator.Commit(_attempt!); - } - - [Fact] - public void should_dispose_the_underlying_persistence() - { - A.CallTo(() => persistence.Commit(_attempt!)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_from_checkpoint : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFrom(0)).Returns([_commit]); - } - - protected override void Because() - { - Decorator.GetFrom(0).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFrom(0)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_in_a_bucket_from_checkpoint : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFrom(Bucket.Default, 0)).Returns([_commit]); - } - - protected override void Because() - { - Decorator.GetFrom(Bucket.Default, 0).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFrom(Bucket.Default, 0)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging : using_underlying_persistence - { - private IPipelineHook? _hook; - private IPipelineHookAsync? _hookAsync; - - protected override void Context() - { - _hook = A.Fake(); - pipelineHooks.Add(_hook); - - _hookAsync = A.Fake(); - pipelineHooksAsync.Add(_hookAsync); - } - - protected override void Because() - { - Decorator.Purge(); - } - - [Fact] - public void should_call_the_pipeline_hook_purge() - { - A.CallTo(() => _hook!.OnPurge(null)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_call_the_async_pipeline_hook_purge() - { - A.CallTo(() => _hookAsync!.OnPurgeAsync(null, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_purging_a_bucket : using_underlying_persistence - { - private IPipelineHook? _hook; - private IPipelineHookAsync? _hookAsync; - private const string _bucketId = "Bucket"; - - protected override void Context() - { - _hook = A.Fake(); - pipelineHooks.Add(_hook); - - _hookAsync = A.Fake(); - pipelineHooksAsync.Add(_hookAsync); - } - - protected override void Because() - { - Decorator.Purge(_bucketId); - } - - [Fact] - public void should_call_the_pipeline_hook_purge() - { - A.CallTo(() => _hook!.OnPurge(_bucketId)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_call_the_async_pipeline_hook_purge() - { - A.CallTo(() => _hookAsync!.OnPurgeAsync(_bucketId, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_deleting_a_stream : using_underlying_persistence - { - private IPipelineHook? _hook; - private IPipelineHookAsync? _hookAsync; - private const string _bucketId = "Bucket"; - private const string _streamId = "Stream"; - - protected override void Context() - { - _hook = A.Fake(); - pipelineHooks.Add(_hook); - - _hookAsync = A.Fake(); - pipelineHooksAsync.Add(_hookAsync); - } - - protected override void Because() - { - Decorator.DeleteStream(_bucketId, _streamId); - } - - [Fact] - public void should_call_the_pipeline_hook_purge() - { - A.CallTo(() => _hook!.OnDeleteStream(_bucketId, _streamId)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_call_the_async_pipeline_hook_purge() - { - A.CallTo(() => _hookAsync!.OnDeleteStreamAsync(_bucketId, _streamId, A.Ignored)).MustHaveHappenedOnceExactly(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_from_checkpoint_with_filtering_PipelineHook_Sync : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private List? _commits; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(null); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFrom(0)).Returns([_commit]); - } - - protected override void Because() - { - _commits = Decorator.GetFrom(0).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFrom(0)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_first_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_not_pass_the_events_through_the_second_pipeline_hooks() - { - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_pass_the_events_through_the_first_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); - } - - [Fact] - public void should_not_pass_the_events_through_the_second_async_pipeline_hooks() - { - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); - } - - [Fact] - public void commit_list_should_be_empty() - { - _commits.Should().BeEmpty(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_reading_the_all_events_from_checkpoint_with_filtering_PipelineHook_Async : using_underlying_persistence - { - private ICommit? _commit; - private IPipelineHook? _hook1; - private IPipelineHook? _hook2; - private IPipelineHookAsync? _hook1Async; - private IPipelineHookAsync? _hook2Async; - private List? _commits; - - protected override void Context() - { - _commit = new Commit(Bucket.Default, streamId, 1, Guid.NewGuid(), 1, DateTime.Now, 1, null, null); - - _hook1 = A.Fake(); - A.CallTo(() => _hook1.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook1); - - _hook2 = A.Fake(); - A.CallTo(() => _hook2.SelectCommit(_commit)).Returns(_commit); - pipelineHooks.Add(_hook2); - - _hook1Async = A.Fake(); - A.CallTo(() => _hook1Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(null)); - pipelineHooksAsync.Add(_hook1Async); - - _hook2Async = A.Fake(); - A.CallTo(() => _hook2Async.SelectCommitAsync(_commit, A.Ignored)).Returns(Task.FromResult(_commit)); - pipelineHooksAsync.Add(_hook2Async); - - A.CallTo(() => persistence.GetFrom(0)).Returns([_commit]); - } - - protected override void Because() - { - _commits = Decorator.GetFrom(0).ToList(); - } - - [Fact] - public void should_call_the_underlying_persistence_to_get_events() - { - A.CallTo(() => persistence.GetFrom(0)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_all_events_through_the_first_pipeline_hooks() - { - A.CallTo(() => _hook1!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_the_events_through_the_second_pipeline_hooks() - { - A.CallTo(() => _hook2!.SelectCommit(_commit!)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_pass_the_events_through_the_first_async_pipeline_hooks() - { - A.CallTo(() => _hook1Async!.SelectCommitAsync(_commit!, A.Ignored)).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void should_not_pass_the_events_through_the_second_async_pipeline_hooks() - { - A.CallTo(() => _hook2Async!.SelectCommitAsync(_commit!, A.Ignored)).MustNotHaveHappened(); - } - - [Fact] - public void commit_list_should_be_empty() - { - _commits.Should().BeEmpty(); - } - } - - public abstract class using_underlying_persistence : SpecificationBase - { - private PipelineHooksAwarePersistStreamsDecorator? decorator; - protected readonly IPersistStreams persistence = A.Fake(); - protected readonly List pipelineHooks = []; - protected readonly List pipelineHooksAsync = []; - protected readonly string streamId = Guid.NewGuid().ToString(); - - public PipelineHooksAwarePersistStreamsDecorator Decorator - { - get { return decorator ??= new PipelineHooksAwarePersistStreamsDecorator(persistence, pipelineHooks.Select(x => x), pipelineHooksAsync.Select(x => x)); } - set { decorator = value; } - } - } -} - -#pragma warning restore IDE1006 // Naming Styles -// ReSharper enable InconsistentNaming -#pragma warning restore 169 \ No newline at end of file diff --git a/src/NEventStore.Tests/Properties/AssemblyInfo.cs b/src/NEventStore.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index cee49ba8b..000000000 --- a/src/NEventStore.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEventStore.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEventStore")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM - -[assembly: Guid("ce2d3ce5-5061-43e0-8b5b-6e14c8ab0e7b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -//[assembly: AssemblyVersion("8.0.0.0")] -//[assembly: AssemblyFileVersion("8.0.0.0")] -//[assembly: AssemblyInformationalVersion("8.0.0-beta.5+Branch.release-8.0.0.Sha.4029904d0dae687763bdf2f11ad4c7754927a8c6")] diff --git a/src/NEventStore.Tests/SerializationExtensionsTests.cs b/src/NEventStore.Tests/SerializationExtensionsTests.cs deleted file mode 100644 index 90c51e0ec..000000000 --- a/src/NEventStore.Tests/SerializationExtensionsTests.cs +++ /dev/null @@ -1,169 +0,0 @@ -using FluentAssertions; -using NEventStore.Persistence.AcceptanceTests.BDD; -using NEventStore.Serialization; -#if MSTEST -using Microsoft.VisualStudio.TestTools.UnitTesting; -#endif -#if XUNIT -using Xunit; -using Xunit.Should; -#endif - -#pragma warning disable IDE1006 // Naming Styles - -namespace NEventStore -{ -#if MSTEST - [TestClass] -#endif - public class when_serializing_with_a_serializer_that_disposes_the_output_stream - { - private readonly byte[] _payload = [1, 2, 3, 4, 5]; - - [Fact] - public void should_return_the_serialized_bytes() - { - var serialized = new DisposingSerializer(_payload).Serialize(new object()); - - serialized.Should().Equal(_payload); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_serializing_a_large_payload_written_in_many_chunks - { - private readonly byte[] _payload = Enumerable.Range(0, 128 * 1024) - .Select(i => (byte)(i % 251)) - .ToArray(); - - [Fact] - public void should_return_an_exact_byte_array() - { - var serialized = new ChunkedSerializer(_payload, chunkSize: 37).Serialize(new object()); - - serialized.Should().Equal(_payload); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_serializing_and_inspecting_the_output_stream - { - [Fact] - public void should_use_the_target_specific_output_stream() - { - var serializer = new InspectingSerializer(); - - serializer.Serialize(new object()); - -#if NET8_0_OR_GREATER - serializer.OutputStreamTypeName.Should().Be("PooledWriteStream"); - serializer.OutputCanRead.Should().BeFalse(); - serializer.OutputCanSeek.Should().BeFalse(); -#else - serializer.OutputStreamTypeName.Should().Be(nameof(MemoryStream)); - serializer.OutputCanRead.Should().BeTrue(); - serializer.OutputCanSeek.Should().BeTrue(); -#endif - serializer.OutputCanWrite.Should().BeTrue(); - } - } - -#if MSTEST - [TestClass] -#endif - public class when_serializing_one_byte_at_a_time - { - private readonly byte[] _payload = Enumerable.Range(0, 1024) - .Select(i => (byte)(i % 251)) - .ToArray(); - - [Fact] - public void should_return_an_exact_byte_array() - { - var serialized = new ByteByByteSerializer(_payload).Serialize(new object()); - - serialized.Should().Equal(_payload); - } - } - - internal sealed class DisposingSerializer(byte[] payload) : ISerialize - { - public void Serialize(Stream output, T graph) where T : notnull - { - output.Write(payload, 0, payload.Length); - // Several production serializers dispose wrapper writers that close the supplied - // stream. The extension owns the final byte[] materialization, so this behavior must - // stay safe for both MemoryStream and the modern pooled stream implementation. - output.Dispose(); - } - - public T? Deserialize(Stream input) - { - throw new NotSupportedException(); - } - } - - internal sealed class InspectingSerializer : ISerialize - { - public string? OutputStreamTypeName { get; private set; } - - public bool OutputCanRead { get; private set; } - - public bool OutputCanSeek { get; private set; } - - public bool OutputCanWrite { get; private set; } - - public void Serialize(Stream output, T graph) where T : notnull - { - OutputStreamTypeName = output.GetType().Name; - OutputCanRead = output.CanRead; - OutputCanSeek = output.CanSeek; - OutputCanWrite = output.CanWrite; - output.WriteByte(42); - } - - public T? Deserialize(Stream input) - { - throw new NotSupportedException(); - } - } - - internal sealed class ChunkedSerializer(byte[] payload, int chunkSize) : ISerialize - { - public void Serialize(Stream output, T graph) where T : notnull - { - for (int offset = 0; offset < payload.Length; offset += chunkSize) - { - int count = Math.Min(chunkSize, payload.Length - offset); - output.Write(payload, offset, count); - } - } - - public T? Deserialize(Stream input) - { - throw new NotSupportedException(); - } - } - - internal sealed class ByteByByteSerializer(byte[] payload) : ISerialize - { - public void Serialize(Stream output, T graph) where T : notnull - { - foreach (byte value in payload) - { - output.WriteByte(value); - } - } - - public T? Deserialize(Stream input) - { - throw new NotSupportedException(); - } - } -} - -#pragma warning restore IDE1006 // Naming Styles diff --git a/src/NEventStore.Tests/TestableWireup.cs b/src/NEventStore.Tests/TestableWireup.cs deleted file mode 100644 index 7c8305b24..000000000 --- a/src/NEventStore.Tests/TestableWireup.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace NEventStore.Tests -{ - public class TestableWireup : Wireup - { - public TestableWireup(Wireup inner) : base(inner) - { - } - - public new NanoContainer Container - { - get { return base.Container; } - } - } -} diff --git a/src/NEventStore.Tests/TestableWireupExtensions.cs b/src/NEventStore.Tests/TestableWireupExtensions.cs deleted file mode 100644 index 9dd623511..000000000 --- a/src/NEventStore.Tests/TestableWireupExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NEventStore.Tests -{ - public static class TestableWireupExtensions - { - public static TestableWireup UseTestableWireup(this Wireup wireup) - { - return new TestableWireup(wireup); - } - } -} diff --git a/src/NEventStore/AccessSnapshotsExtensions.cs b/src/NEventStore/AccessSnapshotsExtensions.cs deleted file mode 100644 index 1016e9379..000000000 --- a/src/NEventStore/AccessSnapshotsExtensions.cs +++ /dev/null @@ -1,126 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Provides extension methods for and . - /// - public static class AccessSnapshotsExtensions - { - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated from the default bucket. - /// - /// The instance. - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - public static ISnapshot? GetSnapshot(this IAccessSnapshots accessSnapshots, Guid streamId, int maxRevision) - { - return GetSnapshot(accessSnapshots, streamId.ToString(), maxRevision); - } - - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated from the default bucket. - /// - /// The instance. - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - public static ISnapshot? GetSnapshot(this IAccessSnapshots accessSnapshots, string streamId, int maxRevision) - { - return accessSnapshots.GetSnapshot(Bucket.Default, streamId, maxRevision); - } - - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated. - /// - /// The instance. - /// The value which uniquely identifies bucket the stream belongs to. - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - public static ISnapshot? GetSnapshot(this IAccessSnapshots accessSnapshots, string bucketId, Guid streamId, int maxRevision) - { - return accessSnapshots.GetSnapshot(bucketId, streamId.ToString(), maxRevision); - } - - /// - /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified for the default bucket. - /// - /// The instance. - /// The maximum difference between the head and most recent snapshot revisions. - /// The streams for which the head and snapshot revisions differ by at least the threshold specified. - /// - /// - public static IEnumerable GetStreamsToSnapshot(this IAccessSnapshots accessSnapshots, int maxThreshold) - { - return accessSnapshots.GetStreamsToSnapshot(Bucket.Default, maxThreshold); - } - - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated from the default bucket. - /// - /// The instance. - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// The token to monitor for cancellation requests. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - public static Task GetSnapshotAsync(this IAccessSnapshotsAsync accessSnapshots, Guid streamId, int maxRevision, CancellationToken cancellationToken = default) - { - return GetSnapshotAsync(accessSnapshots, streamId.ToString(), maxRevision, cancellationToken); - } - - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated from the default bucket. - /// - /// The instance. - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// The token to monitor for cancellation requests. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - public static Task GetSnapshotAsync(this IAccessSnapshotsAsync accessSnapshots, string streamId, int maxRevision, CancellationToken cancellationToken = default) - { - return accessSnapshots.GetSnapshotAsync(Bucket.Default, streamId, maxRevision, cancellationToken); - } - - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated. - /// - /// The instance. - /// The value which uniquely identifies bucket the stream belongs to. - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// The token to monitor for cancellation requests. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - public static Task GetSnapshotAsync(this IAccessSnapshotsAsync accessSnapshots, string bucketId, Guid streamId, int maxRevision, CancellationToken cancellationToken = default) - { - return accessSnapshots.GetSnapshotAsync(bucketId, streamId.ToString(), maxRevision, cancellationToken); - } - - /// - /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified for the default bucket. - /// - /// The instance. - /// The maximum difference between the head and most recent snapshot revisions. - /// The observer to receive the streams. - /// The token to monitor for cancellation requests. - /// - /// - public static Task GetStreamsToSnapshotAsync(this IAccessSnapshotsAsync accessSnapshots, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default) - { - return accessSnapshots.GetStreamsToSnapshotAsync(Bucket.Default, maxThreshold, asyncObserver, cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Bucket.cs b/src/NEventStore/Bucket.cs deleted file mode 100644 index 7ddcc95c8..000000000 --- a/src/NEventStore/Bucket.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NEventStore -{ - /// - /// Provides a way to identify a bucket. - /// - public static class Bucket - { - /// - /// The default bucket Id. - /// - public const string Default = "default"; - } -} \ No newline at end of file diff --git a/src/NEventStore/CommitAttempt.cs b/src/NEventStore/CommitAttempt.cs deleted file mode 100644 index c59c25d10..000000000 --- a/src/NEventStore/CommitAttempt.cs +++ /dev/null @@ -1,136 +0,0 @@ -#pragma warning disable RCS1170 // Use read-only auto-implemented property. - -namespace NEventStore -{ - /// - /// Commit Attempt represents a single attempt to commit a set of events to a stream. - /// - public class CommitAttempt - { - /// - /// Initializes a new instance of the Commit class for the default bucket. - /// - /// The value which uniquely identifies the stream in a bucket to which the commit belongs. - /// The value which indicates the revision of the most recent event in the stream to which this commit applies. - /// The value which uniquely identifies the commit within the stream. - /// The value which indicates the sequence (or position) in the stream to which this commit applies. - /// The point in time at which the commit was persisted. - /// The metadata which provides additional, unstructured information about this commit. - /// The collection of event messages to be committed as a single unit. - public CommitAttempt( - Guid streamId, - int streamRevision, - Guid commitId, - int commitSequence, - DateTime commitStamp, - IDictionary? headers, - ICollection events) - : this(Bucket.Default, streamId.ToString(), streamRevision, commitId, commitSequence, commitStamp, headers, events) - { } - - /// - /// Initializes a new instance of the Commit class for the default bucket. - /// - /// The value which uniquely identifies the stream in a bucket to which the commit belongs. - /// The value which indicates the revision of the most recent event in the stream to which this commit applies. - /// The value which uniquely identifies the commit within the stream. - /// The value which indicates the sequence (or position) in the stream to which this commit applies. - /// The point in time at which the commit was persisted. - /// The metadata which provides additional, unstructured information about this commit. - /// The collection of event messages to be committed as a single unit. - public CommitAttempt( - string streamId, - int streamRevision, - Guid commitId, - int commitSequence, - DateTime commitStamp, - IDictionary? headers, - ICollection events) - : this(Bucket.Default, streamId, streamRevision, commitId, commitSequence, commitStamp, headers, events) - { } - - /// - /// Initializes a new instance of the Commit class. - /// - /// The value which identifies bucket to which the stream and the commit belongs - /// The value which uniquely identifies the stream in a bucket to which the commit belongs. - /// The value which indicates the revision of the most recent event in the stream to which this commit applies. - /// The value which uniquely identifies the commit within the stream. - /// The value which indicates the sequence (or position) in the stream to which this commit applies. - /// The point in time at which the commit was persisted. - /// The metadata which provides additional, unstructured information about this commit. - /// The collection of event messages to be committed as a single unit. - public CommitAttempt( - string bucketId, - string streamId, - int streamRevision, - Guid commitId, - int commitSequence, - DateTime commitStamp, - IDictionary? headers, - ICollection events) - { - Guard.NotNullOrWhiteSpace(() => bucketId, bucketId); - Guard.NotNullOrWhiteSpace(() => streamId, streamId); - Guard.NotLessThanOrEqualTo(() => streamRevision, streamRevision, 0); - Guard.NotDefault(() => commitId, commitId); - Guard.NotLessThanOrEqualTo(() => commitSequence, commitSequence, 0); - Guard.NotLessThan(() => commitSequence, streamRevision, 0); - Guard.NotEmpty(() => events, events); - - BucketId = bucketId; - StreamId = streamId; - StreamRevision = streamRevision; - CommitId = commitId; - CommitSequence = commitSequence; - CommitStamp = commitStamp; - Headers = headers ?? new Dictionary(); - Events = events; - //Events = events == null ? - // new ReadOnlyCollection(new List()) : - // new ReadOnlyCollection(events.ToList()); - } - - /// - /// Gets the value which identifies bucket to which the stream and the commit belongs. - /// - public string BucketId { get; private set; } - - /// - /// Gets the value which uniquely identifies the stream to which the commit belongs. - /// - public string StreamId { get; private set; } - - /// - /// Gets the value which indicates the revision of the most recent event in the stream to which this commit applies. - /// - public int StreamRevision { get; private set; } - - /// - /// Gets the value which uniquely identifies the commit within the stream. - /// - public Guid CommitId { get; private set; } - - /// - /// Gets the value which indicates the sequence (or position) in the stream to which this commit applies. - /// - public int CommitSequence { get; private set; } - - /// - /// Gets the point in time at which the commit was persisted. - /// - public DateTime CommitStamp { get; private set; } - - /// - /// Gets the metadata which provides additional, unstructured information about this commit. - /// - public IDictionary Headers { get; private set; } - - /// - /// Gets the collection of event messages to be committed as a single unit. - /// - public ICollection Events { get; private set; } - } -} - -#pragma warning restore RCS1170 // Use read-only auto-implemented property. \ No newline at end of file diff --git a/src/NEventStore/CommitEqualityComparer.cs b/src/NEventStore/CommitEqualityComparer.cs deleted file mode 100644 index ae91bab04..000000000 --- a/src/NEventStore/CommitEqualityComparer.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace NEventStore -{ - /// - /// Compares two commits for equality based on their bucket identity, stream identity, and commit identity. - /// - public sealed class CommitEqualityComparer : IEqualityComparer - { - /// - public bool Equals(ICommit? x, ICommit? y) - { - if (ReferenceEquals(x, y)) - { - return true; - } - if (x is null) - { - return false; - } - if (y is null) - { - return false; - } - if (x.GetType() != y.GetType()) - { - return false; - } - return string.Equals(x.BucketId, y.BucketId, StringComparison.Ordinal) - && string.Equals(x.StreamId, y.StreamId, StringComparison.Ordinal) - && string.Equals(x.CommitId, y.CommitId); - } - - /// - public int GetHashCode(ICommit obj) - { - unchecked - { - int hashCode = obj.BucketId?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ (obj.StreamId?.GetHashCode() ?? 0); - return (hashCode * 397) ^ obj.CommitId.GetHashCode(); - } - } - } -} \ No newline at end of file diff --git a/src/NEventStore/CommitEventsExtensions.cs b/src/NEventStore/CommitEventsExtensions.cs deleted file mode 100644 index f10af7a8c..000000000 --- a/src/NEventStore/CommitEventsExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Provides extension methods for and instances. - /// - public static class CommitEventsExtensions - { - /// - /// Gets the corresponding commits from the stream indicated starting at the revision specified until the - /// end of the stream sorted in ascending order--from oldest to newest from the default bucket. - /// - /// The instance. - /// The stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events from the stream specified sorted in ascending order. - /// - /// - public static IEnumerable GetFrom(this ICommitEvents commitEvents, string streamId, int minRevision, int maxRevision) - { - return commitEvents.GetFrom(Bucket.Default, streamId, minRevision, maxRevision); - } - - /// - /// Gets the corresponding commits from the stream indicated starting at the revision specified until the - /// end of the stream sorted in ascending order--from oldest to newest from the default bucket. - /// - /// The instance. - /// The stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// Observer to receive the commits. - /// The token to monitor for cancellation requests. - /// - /// - public static Task GetFromAsync(this ICommitEventsAsync commitEvents, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken = default) - { - return commitEvents.GetFromAsync(Bucket.Default, streamId, minRevision, maxRevision, observer, cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/CommitStreamObserver.cs b/src/NEventStore/CommitStreamObserver.cs deleted file mode 100644 index d91026f16..000000000 --- a/src/NEventStore/CommitStreamObserver.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Runtime.ExceptionServices; - -namespace NEventStore -{ - /// - /// Represents an async observer that can receive and stores commits from a stream. - /// Can be used as base class for other observers. - /// - public class CommitStreamObserver : IAsyncObserver - { - /// - /// The list of commits read from the stream. - /// - public IList Commits { get; } = []; - - /// - /// Indicates if the read operation has completed. - /// - public bool ReadCompleted { get; private set; } - - /// - /// Store the commits received from the stream in the collection. - /// - public virtual Task OnNextAsync(ICommit value, CancellationToken cancellationToken) - { - Commits.Add(value); - return Task.FromResult(true); - } - - /// - /// Notifies the observer that the provider has experienced an error condition. - /// - /// Preserve the stack trace and rethrow the exception that occurred while reading commits from the stream. - /// - /// - /// Override this method to log and handle the error. - /// - /// - public virtual Task OnErrorAsync(Exception ex, CancellationToken cancellationToken) - { - // Preserve the stack trace and rethrow the exception - ExceptionDispatchInfo.Capture(ex).Throw(); - return Task.CompletedTask; - } - - /// - public virtual Task OnCompletedAsync(CancellationToken cancellationToken) - { - ReadCompleted = true; - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/src/NEventStore/ConcurrencyException.cs b/src/NEventStore/ConcurrencyException.cs deleted file mode 100644 index e673e0ebc..000000000 --- a/src/NEventStore/ConcurrencyException.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NEventStore -{ - /// - /// Represents an optimistic concurrency conflict between multiple writers. - /// - public class ConcurrencyException : Exception - { - /// - /// Initializes a new instance of the ConcurrencyException class. - /// - public ConcurrencyException() - { } - - /// - /// Initializes a new instance of the ConcurrencyException class. - /// - /// The message that describes the error. - public ConcurrencyException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the ConcurrencyException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public ConcurrencyException(string message, Exception innerException) - : base(message, innerException) - { } - } -} \ No newline at end of file diff --git a/src/NEventStore/Conversion/EventUpconverterPipelineHook.cs b/src/NEventStore/Conversion/EventUpconverterPipelineHook.cs deleted file mode 100644 index d5913ea2d..000000000 --- a/src/NEventStore/Conversion/EventUpconverterPipelineHook.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Logging; -using NEventStore.Persistence; - -namespace NEventStore.Conversion -{ - /// - /// Represents a pipeline hook that upconverts events. - /// - public class EventUpconverterPipelineHook : PipelineHookBase - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(EventUpconverterPipelineHook)); - private readonly IDictionary> _converters; - - /// - /// Initializes a new instance of the EventUpconverterPipelineHook class. - /// - /// - public EventUpconverterPipelineHook(IDictionary> converters) - { - _converters = converters ?? throw new ArgumentNullException(nameof(converters)); - } - - /// - public override ICommit? SelectCommit(ICommit committed) - { - EventMessage[]? eventMessages = null; - int index = 0; - foreach (var eventMessage in committed.Events) - { - object convert = Convert(eventMessage.Body); - if (!ReferenceEquals(convert, eventMessage.Body)) - { - // Most streams that flow through an upconverter do not necessarily contain - // events handled by the registered converters. Defer the output array until the - // first actual conversion so the no-conversion read path returns the original - // commit without allocating or copying every event message. - eventMessages ??= CopyEventMessages(committed.Events); - eventMessages[index] = new EventMessage { Headers = eventMessage.Headers, Body = convert }; - } - - index++; - } - - if (eventMessages == null) - { - return committed; - } - - return new Commit(committed.BucketId, - committed.StreamId, - committed.StreamRevision, - committed.CommitId, - committed.CommitSequence, - committed.CommitStamp, - committed.CheckpointToken, - committed.Headers, - eventMessages); - } - - private static EventMessage[] CopyEventMessages(ICollection events) - { - var eventMessages = new EventMessage[events.Count]; - events.CopyTo(eventMessages, 0); - return eventMessages; - } - - /// - protected override void Dispose(bool disposing) - { - _converters.Clear(); - base.Dispose(disposing); - } - - private object Convert(object source) - { - Type sourceType = source.GetType(); - if (!_converters.TryGetValue(sourceType, out Func? converter)) - { - return source; - } - - object target = converter(source); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.ConvertingEvent, sourceType, target.GetType()); - } - - return Convert(target); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Conversion/IUpconvertEvents.cs b/src/NEventStore/Conversion/IUpconvertEvents.cs deleted file mode 100644 index 8b4323cb5..000000000 --- a/src/NEventStore/Conversion/IUpconvertEvents.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace NEventStore.Conversion -{ - /// - /// Provides the ability to upconvert an event from one type to another. - /// - /// The source event type from which to convert. - /// The target event type. - public interface IUpconvertEvents - where TSource : class - where TTarget : class - { - /// - /// Converts an event from one type to another. - /// - /// The event to be converted. - /// The converted event. - TTarget Convert(TSource sourceEvent); - } -} \ No newline at end of file diff --git a/src/NEventStore/Conversion/MultipleConvertersFoundException.cs b/src/NEventStore/Conversion/MultipleConvertersFoundException.cs deleted file mode 100644 index 30dd265a9..000000000 --- a/src/NEventStore/Conversion/MultipleConvertersFoundException.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NEventStore.Conversion -{ - /// - /// Represents the failure that occurs when there are two or more event converters created for the same source type. - /// - public class MultipleConvertersFoundException : Exception - { - /// - /// Initializes a new instance of the MultipleConvertersFoundException class. - /// - public MultipleConvertersFoundException() - { } - - /// - /// Initializes a new instance of the MultipleConvertersFoundException class. - /// - /// The message that describes the error. - public MultipleConvertersFoundException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the MultipleConvertersFoundException class. - /// - /// The message that describes the error. - /// The exception that is the cause of the current exception. - public MultipleConvertersFoundException(string message, Exception innerException) - : base(message, innerException) - { } - } -} \ No newline at end of file diff --git a/src/NEventStore/Diagnostics/PerformanceCounterPersistenceEngine.cs b/src/NEventStore/Diagnostics/PerformanceCounterPersistenceEngine.cs deleted file mode 100644 index cfc010d50..000000000 --- a/src/NEventStore/Diagnostics/PerformanceCounterPersistenceEngine.cs +++ /dev/null @@ -1,261 +0,0 @@ -using System.Diagnostics; -using NEventStore.Persistence; - -namespace NEventStore.Diagnostics -{ - // PerformanceCounters are not cross platform - -#if NET462 - - /// - /// Decorate an IPersistStreams implementation with performance counters. - /// - public class PerformanceCounterPersistenceEngine : IPersistStreams - { - private readonly PerformanceCounters _counters; - private readonly IPersistStreams _persistence; - - /// - /// Initializes a new instance of the PerformanceCounterPersistenceEngine class. - /// - public PerformanceCounterPersistenceEngine(IPersistStreams persistence, string instanceName) - { - _persistence = persistence; - _counters = new PerformanceCounters(instanceName); - } - - /// - public void Initialize() - { - _persistence.Initialize(); - } - - /// - public IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision) - { - return _persistence.GetFrom(bucketId, streamId, minRevision, maxRevision); - } - - /// - public ICommit? Commit(CommitAttempt attempt) - { - Stopwatch clock = Stopwatch.StartNew(); - var commit = _persistence.Commit(attempt); - clock.Stop(); - _counters.CountCommit(attempt.Events.Count, clock.ElapsedMilliseconds); - return commit; - } - - /// - public Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken) - { - return _persistence.GetFromAsync(bucketId, streamId, minRevision, maxRevision, observer, cancellationToken); - } - - /// - public async Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) - { - Stopwatch clock = Stopwatch.StartNew(); - var commit = await _persistence.CommitAsync(attempt, cancellationToken).ConfigureAwait(false); - clock.Stop(); - _counters.CountCommit(attempt.Events.Count, clock.ElapsedMilliseconds); - return commit; - } - - /// - [Obsolete("DateTime is problematic in distributed systems. Use GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) instead. This method will be removed in a later version.")] - public IEnumerable GetFromTo(string bucketId, DateTime startDate, DateTime endDate) - { - return _persistence.GetFromTo(bucketId, startDate, endDate); - } - - /// - [Obsolete("DateTime is problematic in distributed systems. Use GetFrom(Int64 checkpointToken) instead. This method will be removed in a later version.")] - public IEnumerable GetFrom(string bucketId, DateTime startDate) - { - return _persistence.GetFrom(bucketId, startDate); - } - - /// - public IEnumerable GetFrom(Int64 checkpointToken) - { - return _persistence.GetFrom(checkpointToken); - } - - /// - public Task GetFromAsync(Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return _persistence.GetFromAsync(checkpointToken, asyncObserver, cancellationToken); - } - - /// - public IEnumerable GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) - { - return _persistence.GetFromTo(fromCheckpointToken, toCheckpointToken); - } - - /// - public Task GetFromToAsync(Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return _persistence.GetFromToAsync(fromCheckpointToken, toCheckpointToken, asyncObserver, cancellationToken); - } - - /// - public IEnumerable GetFrom(string bucketId, Int64 checkpointToken) - { - return _persistence.GetFrom(bucketId, checkpointToken); - } - - /// - public Task GetFromAsync(string bucketId, Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return _persistence.GetFromAsync(bucketId, checkpointToken, asyncObserver, cancellationToken); - } - - /// - public IEnumerable GetFromTo(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken) - { - return _persistence.GetFromTo(bucketId, fromCheckpointToken, toCheckpointToken); - } - - /// - public Task GetFromToAsync(string bucketId, long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return _persistence.GetFromToAsync(bucketId, fromCheckpointToken, toCheckpointToken, asyncObserver, cancellationToken); - } - - /// - public ISnapshot? GetSnapshot(string bucketId, string streamId, int maxRevision) - { - return _persistence.GetSnapshot(bucketId, streamId, maxRevision); - } - - /// - public bool AddSnapshot(ISnapshot snapshot) - { - bool result = _persistence.AddSnapshot(snapshot); - if (result) - { - _counters.CountSnapshot(); - } - - return result; - } - - /// - public virtual IEnumerable GetStreamsToSnapshot(string bucketId, int maxThreshold) - { - return _persistence.GetStreamsToSnapshot(bucketId, maxThreshold); - } - - /// - public Task GetSnapshotAsync(string bucketId, string streamId, int maxRevision, CancellationToken cancellationToken) - { - return _persistence.GetSnapshotAsync(bucketId, streamId, maxRevision, cancellationToken); - } - - /// - public async Task AddSnapshotAsync(ISnapshot snapshot, CancellationToken cancellationToken) - { - bool result = await _persistence.AddSnapshotAsync(snapshot, cancellationToken).ConfigureAwait(false); - if (result) - { - _counters.CountSnapshot(); - } - - return result; - } - - /// - public Task GetStreamsToSnapshotAsync(string bucketId, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return _persistence.GetStreamsToSnapshotAsync(bucketId, maxThreshold, asyncObserver, cancellationToken); - } - - /// - public virtual void Purge() - { - _persistence.Purge(); - } - - /// - public void Purge(string bucketId) - { - _persistence.Purge(bucketId); - } - - /// - public Task PurgeAsync(CancellationToken cancellationToken) - { - return _persistence.PurgeAsync(cancellationToken); - } - - /// - public Task PurgeAsync(string bucketId, CancellationToken cancellationToken) - { - return _persistence.PurgeAsync(bucketId, cancellationToken); - } - - /// - public void Drop() - { - _persistence.Drop(); - } - - /// - public void DeleteStream(string bucketId, string streamId) - { - _persistence.DeleteStream(bucketId, streamId); - } - - /// - public Task DeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken) - { - return _persistence.DeleteStreamAsync(bucketId, streamId, cancellationToken); - } - - /// - public bool IsDisposed - { - get { return _persistence.IsDisposed; } - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Finalizes an instance of the PerformanceCounterPersistenceEngine class. - /// - ~PerformanceCounterPersistenceEngine() - { - Dispose(false); - } - - /// - /// Dispose the performance counter and the wrapped persistence engine. - /// - protected virtual void Dispose(bool disposing) - { - if (!disposing) - { - return; - } - - _counters.Dispose(); - _persistence.Dispose(); - } - - /// - /// Unwrap the performance counter and return the wrapped persistence engine. - /// - public IPersistStreams UnwrapPersistenceEngine() - { - return _persistence; - } - } -#endif -} \ No newline at end of file diff --git a/src/NEventStore/Diagnostics/PerformanceCounters.cs b/src/NEventStore/Diagnostics/PerformanceCounters.cs deleted file mode 100644 index acd420332..000000000 --- a/src/NEventStore/Diagnostics/PerformanceCounters.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Diagnostics; - -namespace NEventStore.Diagnostics -{ - // PerformanceCounters are not cross platform - -#if NET462 - internal class PerformanceCounters : IDisposable - { - private const string CategoryName = "NEventStore"; - private const string TotalCommitsName = "Total Commits"; - private const string CommitsRateName = "Commits/Sec"; - private const string AvgCommitDuration = "Average Commit Duration"; - private const string AvgCommitDurationBase = "Average Commit Duration Base"; - private const string TotalEventsName = "Total Events"; - private const string EventsRateName = "Events/Sec"; - private const string TotalSnapshotsName = "Total Snapshots"; - private const string SnapshotsRateName = "Snapshots/Sec"; - private readonly PerformanceCounter _avgCommitDuration; - private readonly PerformanceCounter _avgCommitDurationBase; - private readonly PerformanceCounter _commitsRate; - private readonly PerformanceCounter _eventsRate; - private readonly PerformanceCounter _snapshotsRate; - private readonly PerformanceCounter _totalCommits; - private readonly PerformanceCounter _totalEvents; - private readonly PerformanceCounter _totalSnapshots; - - static PerformanceCounters() - { - if (PerformanceCounterCategory.Exists(CategoryName)) - { - return; - } - - var counters = new CounterCreationDataCollection - { - new CounterCreationData(TotalCommitsName, "Total number of commits persisted", PerformanceCounterType.NumberOfItems32), - new CounterCreationData(CommitsRateName, "Rate of commits persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), - new CounterCreationData(AvgCommitDuration, "Average duration for each commit", PerformanceCounterType.AverageTimer32), - new CounterCreationData(AvgCommitDurationBase, "Average duration base for each commit", PerformanceCounterType.AverageBase), - new CounterCreationData(TotalEventsName, "Total number of events persisted", PerformanceCounterType.NumberOfItems32), - new CounterCreationData(EventsRateName, "Rate of events persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), - new CounterCreationData(TotalSnapshotsName, "Total number of snapshots persisted", PerformanceCounterType.NumberOfItems32), - new CounterCreationData(SnapshotsRateName, "Rate of snapshots persisted per second", PerformanceCounterType.RateOfCountsPerSecond32), - }; - - // TODO: add other useful counts such as: - // - // * Total Commit Bytes - // * Average Commit Bytes - // * Total Queries - // * Queries Per Second - // * Average Query Duration - // * Commits per Query (Total / average / per second) - // * Events per Query (Total / average / per second) - // - // Some of these will involve hooking into other parts of the NEventStore - - PerformanceCounterCategory.Create(CategoryName, "NEventStore Event-Sourcing Persistence", PerformanceCounterCategoryType.MultiInstance, counters); - } - - public PerformanceCounters(string instanceName) - { - _totalCommits = new PerformanceCounter(CategoryName, TotalCommitsName, instanceName, false); - _commitsRate = new PerformanceCounter(CategoryName, CommitsRateName, instanceName, false); - _avgCommitDuration = new PerformanceCounter(CategoryName, AvgCommitDuration, instanceName, false); - _avgCommitDurationBase = new PerformanceCounter(CategoryName, AvgCommitDurationBase, instanceName, false); - _totalEvents = new PerformanceCounter(CategoryName, TotalEventsName, instanceName, false); - _eventsRate = new PerformanceCounter(CategoryName, EventsRateName, instanceName, false); - _totalSnapshots = new PerformanceCounter(CategoryName, TotalSnapshotsName, instanceName, false); - _snapshotsRate = new PerformanceCounter(CategoryName, SnapshotsRateName, instanceName, false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public void CountCommit(int eventsCount, long elapsedMilliseconds) - { - _totalCommits.Increment(); - _commitsRate.Increment(); - _avgCommitDuration.IncrementBy(elapsedMilliseconds); - _avgCommitDurationBase.Increment(); - _totalEvents.IncrementBy(eventsCount); - _eventsRate.IncrementBy(eventsCount); - } - - public void CountSnapshot() - { - _totalSnapshots.Increment(); - _snapshotsRate.Increment(); - } - - ~PerformanceCounters() - { - Dispose(false); - } - - protected virtual void Dispose(bool disposing) - { - _totalCommits.Dispose(); - _commitsRate.Dispose(); - _avgCommitDuration.Dispose(); - _avgCommitDurationBase.Dispose(); - _totalEvents.Dispose(); - _eventsRate.Dispose(); - _totalSnapshots.Dispose(); - _snapshotsRate.Dispose(); - } - } -#endif -} \ No newline at end of file diff --git a/src/NEventStore/DuplicateCommitException.cs b/src/NEventStore/DuplicateCommitException.cs deleted file mode 100644 index 7a42fda6d..000000000 --- a/src/NEventStore/DuplicateCommitException.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NEventStore -{ - /// - /// Represents an attempt to commit the same information more than once. - /// - public class DuplicateCommitException : Exception - { - /// - /// Initializes a new instance of the DuplicateCommitException class. - /// - public DuplicateCommitException() - {} - - /// - /// Initializes a new instance of the DuplicateCommitException class. - /// - /// The message that describes the error. - public DuplicateCommitException(string message) - : base(message) - {} - - /// - /// Initializes a new instance of the DuplicateCommitException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public DuplicateCommitException(string message, Exception innerException) - : base(message, innerException) - {} - } -} \ No newline at end of file diff --git a/src/NEventStore/EventMessage.cs b/src/NEventStore/EventMessage.cs deleted file mode 100644 index 4294b0c23..000000000 --- a/src/NEventStore/EventMessage.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace NEventStore -{ - using System; - using System.Collections.Generic; - using System.Runtime.Serialization; - - /// - /// Represents a single element in a stream of events. - /// - [Serializable] - [DataContract] - public sealed class EventMessage - { - private Dictionary? _headers; - - /// - /// Initializes a new instance of the EventMessage class. - /// - public EventMessage() - {} - - /// - /// Gets the metadata which provides additional, unstructured information about this message. - /// - /// - /// Headers stay lazily allocated because the common case for an event message is "body only". - /// That removes one dictionary allocation from hot paths such as stream writes while keeping - /// the public contract intact: callers and serializers still observe a non-null, writable - /// dictionary whenever they read the property. The setter remains available so existing - /// serializers can populate the property during deserialization without any custom handling. - /// - [DataMember] - public Dictionary Headers - { - get { return _headers ??= []; } - set { _headers = value; } - } - - /// - /// Gets or sets the actual event message body. - /// - [DataMember] - public object Body { get; set; } - } -} diff --git a/src/NEventStore/EventUpconverterWireup.cs b/src/NEventStore/EventUpconverterWireup.cs deleted file mode 100644 index bd02704d2..000000000 --- a/src/NEventStore/EventUpconverterWireup.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Reflection; -using Microsoft.Extensions.Logging; -using NEventStore.Conversion; -using NEventStore.Logging; - -namespace NEventStore -{ - /// - /// Represents the configuration for event upconverters. - /// - public class EventUpconverterWireup : Wireup - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(EventUpconverterWireup)); - private readonly List _assembliesToScan = new List(); - private readonly Dictionary> _registered = new Dictionary>(); - - /// - /// Initializes a new instance of the EventUpconverterWireup class. - /// - /// - public EventUpconverterWireup(Wireup wireup) : base(wireup) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.EventUpconverterRegistered); - } - - Container.Register(_ => - { - if (_registered.Count > 0) - { - return new EventUpconverterPipelineHook(_registered); - } - - if (_assembliesToScan.Count == 0) - { - _assembliesToScan.AddRange(GetAllAssemblies()); - } - - var converters = GetConverters(_assembliesToScan); - if (converters.Count > 0) - { - return new EventUpconverterPipelineHook(converters); - } - return null; - }); - } - - private static IEnumerable GetAllAssemblies() - { - return Assembly.GetCallingAssembly() - .GetReferencedAssemblies() - .Select(Assembly.Load) - .Concat(new[] { Assembly.GetCallingAssembly() }); - } - - private static Dictionary> GetConverters(IEnumerable toScan) - { - IEnumerable>> c = from a in toScan - from t in a.GetTypes() - where !t.IsAbstract - let i = t.GetInterface(typeof(IUpconvertEvents<,>).FullName!) - where i != null - let sourceType = i.GetGenericArguments().First() - let convertMethod = i.GetMethods(BindingFlags.Public | BindingFlags.Instance).First() - let instance = Activator.CreateInstance(t) - select new KeyValuePair>( - sourceType, e => convertMethod.Invoke(instance, new[] { e })!); - try - { - return c.ToDictionary(x => x.Key, x => x.Value); - } - catch (ArgumentException e) - { - throw new MultipleConvertersFoundException(e.Message, e); - } - } - - /// - /// Scans the specified assemblies for event upconverters. - /// - public virtual EventUpconverterWireup WithConvertersFrom(params Assembly[] assemblies) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.EventUpconvertersLoadedFrom, - string.Join(", ", assemblies.Select(a => $"{a.GetName().Name} {a.GetName().Version}"))); - } - _assembliesToScan.AddRange(assemblies); - return this; - } - - /// - /// Scans the assemblies containing the converters for event upconverters. - /// - public virtual EventUpconverterWireup WithConvertersFromAssemblyContaining(params Type[] converters) - { - IEnumerable assemblies = converters.Select(c => c.Assembly).Distinct(); - return this.WithConvertersFrom(assemblies.ToArray()); - } - - /// - /// Adds a converter to the pipeline. - /// - /// - public virtual EventUpconverterWireup AddConverter( - IUpconvertEvents converter) - where TSource : class - where TTarget : class - { - if (converter == null) - { - throw new ArgumentNullException(nameof(converter)); - } - - _registered[typeof(TSource)] = @event => converter.Convert((TSource)@event); - - return this; - } - } -} \ No newline at end of file diff --git a/src/NEventStore/EventUpconverterWireupExtensions.cs b/src/NEventStore/EventUpconverterWireupExtensions.cs deleted file mode 100644 index 68024e15a..000000000 --- a/src/NEventStore/EventUpconverterWireupExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NEventStore -{ - /// - /// Provides support for upconverting events during the commit phase. - /// - public static class EventUpconverterWireupExtensions - { - /// - /// Configures the event store to upconvert events during the commit phase. - /// - public static EventUpconverterWireup UsingEventUpconversion(this Wireup wireup) - { - return new EventUpconverterWireup(wireup); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/ExtensionMethods.cs b/src/NEventStore/ExtensionMethods.cs deleted file mode 100644 index b2dcebd11..000000000 --- a/src/NEventStore/ExtensionMethods.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Globalization; - -namespace NEventStore -{ - /// - /// A set of common methods used through the NEventStore. - /// - public static class ExtensionMethods - { - /// - /// Formats the string provided using the values specified. - /// - /// The string to be formatted. - /// The values to be embedded into the string. - /// The formatted string. - public static string FormatWith(this string format, params object[] values) - { - return string.Format(CultureInfo.InvariantCulture, format ?? string.Empty, values); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Guard.cs b/src/NEventStore/Guard.cs deleted file mode 100644 index afbf03429..000000000 --- a/src/NEventStore/Guard.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Linq.Expressions; - -namespace NEventStore -{ - internal static class Guard - { - internal static void NotFalse(bool condition, Func createException) - { - if (!condition) - { - throw createException(); - } - } - - internal static void NotNullOrWhiteSpace(Expression> reference, string value) - { - if (string.IsNullOrWhiteSpace(value)) - { - throw new ArgumentException(GetParameterName(reference)); - } - } - - internal static void NotNull(Expression> reference, T value) - { - if (value == null) - { - throw new ArgumentNullException(GetParameterName(reference)); - } - } - - internal static void NotLessThanOrEqualTo(Expression> reference, T value, T compareTo) - where T: IComparable - { - NotNull(reference, value); - if (value.CompareTo(compareTo) <= 0) - { - throw new ArgumentOutOfRangeException("{0} has value {1} which is less than or equal to {2}".FormatWith(GetParameterName(reference), value, compareTo)); - } - } - - internal static void NotLessThan(Expression> reference, T value, T compareTo) - where T : IComparable - { - NotNull(reference, value); - if (value.CompareTo(compareTo) < 0) - { - throw new ArgumentOutOfRangeException("{0} has value {1} which is less than {2}".FormatWith(GetParameterName(reference), value, compareTo)); - } - } - - internal static void NotDefault(Expression> reference, T value) - where T : IComparable - { - NotNull(reference, value); - if (value.CompareTo(default(T)) == 0) - { - throw new ArgumentException("{0} has value {1} which cannot be equal to it's default value {2}".FormatWith(GetParameterName(reference), value, default(T)!)); - } - } - - internal static void NotEmpty(Expression>> reference, IEnumerable value) - { - NotNull(reference, value); - if (!value.Any()) - { - throw new ArgumentException("{0} cannot be empty".FormatWith(GetParameterName(reference), value, default(T)!)); - } - } - - private static string GetParameterName(LambdaExpression reference) - { - return ((MemberExpression) reference.Body).Member.Name; - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Helpers/DateTimeService.cs b/src/NEventStore/Helpers/DateTimeService.cs deleted file mode 100644 index 3514c4682..000000000 --- a/src/NEventStore/Helpers/DateTimeService.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace NEventStore.Helpers -{ - /// - /// Provides a way to get the current date and time. - /// Useful for testing. - /// - public static class DateTimeService - { - private static Func _nowFunc = () => DateTime.Now; - private static Func _utcNowFunc = () => DateTime.UtcNow; - - /// - /// Gets the current date and time. - /// - public static DateTime Now - { - get - { - return _nowFunc(); - } - } - - /// - /// Gets the current date and time in UTC. - /// - public static DateTime UtcNow - { - get - { - return _utcNowFunc(); - } - } - - #region "test function" - - internal static DisposableAction Override(Func functor) - { - _nowFunc = functor; - _utcNowFunc = () => functor().ToUniversalTime(); - return new DisposableAction(() => - { - _nowFunc = () => DateTime.Now; - _utcNowFunc = () => DateTime.UtcNow; - }); - } - - internal static DisposableAction Override(DateTime overrideNowDate) - { - return Override(() => overrideNowDate); - } - - #endregion - } -} diff --git a/src/NEventStore/Helpers/DisposableAction.cs b/src/NEventStore/Helpers/DisposableAction.cs deleted file mode 100644 index 79c52d8f6..000000000 --- a/src/NEventStore/Helpers/DisposableAction.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NEventStore.Helpers -{ - /// - /// Provides a way to execute an action when disposed. - /// - internal sealed class DisposableAction : IDisposable - { - private Action? _disposeAction; - - public DisposableAction(Action disposeAction) - { - _disposeAction = disposeAction; - } - - public void Dispose() - { - // Interlocked allows the continuation to be executed only once - var dispose = Interlocked.Exchange(ref _disposeAction, null); - dispose?.Invoke(); - } - } -} diff --git a/src/NEventStore/IAccessSnapshots.cs b/src/NEventStore/IAccessSnapshots.cs deleted file mode 100644 index eedc4945e..000000000 --- a/src/NEventStore/IAccessSnapshots.cs +++ /dev/null @@ -1,43 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Indicates the ability to get or retrieve a snapshot for a given stream. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IAccessSnapshots - { - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - ISnapshot? GetSnapshot(string bucketId, string streamId, int maxRevision); - - /// - /// Adds the snapshot provided to the stream indicated. - /// - /// The snapshot to save. - /// If the snapshot was added, returns true; otherwise false. - /// - /// - bool AddSnapshot(ISnapshot snapshot); - - /// - /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The maximum difference between the head and most recent snapshot revisions. - /// The streams for which the head and snapshot revisions differ by at least the threshold specified. - /// - /// - IEnumerable GetStreamsToSnapshot(string bucketId, int maxThreshold); - } -} \ No newline at end of file diff --git a/src/NEventStore/IAccessSnapshotsAsync.cs b/src/NEventStore/IAccessSnapshotsAsync.cs deleted file mode 100644 index 84c5f1111..000000000 --- a/src/NEventStore/IAccessSnapshotsAsync.cs +++ /dev/null @@ -1,49 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Indicates the ability to get or retrieve a snapshot for a given stream. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IAccessSnapshotsAsync - { - /// - /// Gets the most recent snapshot which was taken on or before the revision indicated. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The stream to be searched for a snapshot. - /// The maximum revision possible for the desired snapshot. - /// The token to monitor for cancellation requests. - /// If found, it returns the snapshot; otherwise null is returned. - /// - /// - Task GetSnapshotAsync(string bucketId, string streamId, int maxRevision, CancellationToken cancellationToken = default); - - /// - /// Adds the snapshot provided to the stream indicated. - /// - /// The snapshot to save. - /// The token to monitor for cancellation requests. - /// If the snapshot was added, returns true; otherwise false. - /// - /// - Task AddSnapshotAsync(ISnapshot snapshot, CancellationToken cancellationToken = default); - - /// - /// Gets identifiers for all streams whose head and last snapshot revisions differ by at least the threshold specified. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The maximum difference between the head and most recent snapshot revisions. - /// The observer to receive the streams. - /// The token to monitor for cancellation requests. - /// - /// - /// - /// In Observer.OnErrorAsync and Observer.OnCompletedAsync the checkpoint will always be 0. - /// - Task GetStreamsToSnapshotAsync(string bucketId, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/NEventStore/IAsyncObserver.cs b/src/NEventStore/IAsyncObserver.cs deleted file mode 100644 index c11b6b3e3..000000000 --- a/src/NEventStore/IAsyncObserver.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace NEventStore -{ - /// - /// Represents an async observer that can receive notifications of objects. - /// - public interface IAsyncObserver - { - /// - /// Provides the observer with new data. - /// - /// The current notification information. - /// the cancellation token of the original request. - /// - /// true = continue reading, false = stop reading - /// - Task OnNextAsync(T value, CancellationToken cancellationToken = default); - /// - /// Notifies the observer that the provider has experienced an error condition. - /// - /// The error that has occurred. - /// the cancellation token of the original request. - Task OnErrorAsync(Exception ex, CancellationToken cancellationToken = default); - /// - /// Notifies the observer that the provider has finished sending push-based notifications. - /// It can happen because: - /// - the source has completed sending push-based notifications successfully. - /// - the reading operation has been cancelled. - /// - /// the cancellation token of the original request. - Task OnCompletedAsync(CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/NEventStore/ICommit.cs b/src/NEventStore/ICommit.cs deleted file mode 100644 index 04c731143..000000000 --- a/src/NEventStore/ICommit.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace NEventStore -{ - /// - /// Represents a series of events which have been fully committed as a single unit and which apply to the stream indicated. - /// - public interface ICommit - { - /// - /// Gets the value which identifies bucket to which the stream and the commit belongs. - /// - string BucketId { get; } - - /// - /// Gets the value which uniquely identifies the stream to which the commit belongs. - /// - string StreamId { get; } - - /// - /// Gets the value which indicates the revision of the most recent event in the stream to which this commit applies. - /// - int StreamRevision { get; } - - /// - /// Gets the value which uniquely identifies the commit within the stream. - /// - Guid CommitId { get; } - - /// - /// Gets the value which indicates the sequence (or position) in the stream to which this commit applies. - /// - int CommitSequence { get; } - - /// - /// Gets the point in time at which the commit was persisted. - /// - DateTime CommitStamp { get; } - - /// - /// Gets the metadata which provides additional, unstructured information about this commit. - /// - IDictionary Headers { get; } - - /// - /// Gets the collection of event messages to be committed as a single unit. - /// - ICollection Events { get; } - - /// - /// The checkpoint that represents the storage level order. - /// - Int64 CheckpointToken { get; } - } -} \ No newline at end of file diff --git a/src/NEventStore/ICommitEvents.cs b/src/NEventStore/ICommitEvents.cs deleted file mode 100644 index 69ff1fce0..000000000 --- a/src/NEventStore/ICommitEvents.cs +++ /dev/null @@ -1,39 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Indicates the ability to commit events and access events to and from a given stream. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface ICommitEvents - { - /// - /// Gets the corresponding commits from the stream indicated starting at the revision specified until the - /// end of the stream sorted in ascending order--from oldest to newest. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events from the stream specified sorted in ascending order. - /// - /// - IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision); - - /// - /// Writes the to-be-committed events provided to the underlying persistence mechanism. - /// - /// The series of events and associated metadata to be committed. - /// - /// - /// - /// - /// This interface returns a nullable ICommit object because it's implemented by - /// that can return null if the pipeline hooks decide to abort the commit. - /// - ICommit? Commit(CommitAttempt attempt); - } -} \ No newline at end of file diff --git a/src/NEventStore/ICommitEventsAsync.cs b/src/NEventStore/ICommitEventsAsync.cs deleted file mode 100644 index 2fa4edca4..000000000 --- a/src/NEventStore/ICommitEventsAsync.cs +++ /dev/null @@ -1,47 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Indicates the ability to commit events and access events to and from a given stream. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface ICommitEventsAsync - { - /// - /// Gets the corresponding commits (possibly using an async cursor) from the stream indicated - /// starting at the revision specified until the end of the stream sorted - /// in ascending order--from oldest to newest. - /// Each commit will be passed to an observer. - /// Reading operation will stop: - /// - when the maxRevision is reached. - /// - if CancellationToken is set. - /// - when there are no more commits to read. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// Observer to receive the commits. - /// The token to monitor for cancellation requests. - /// - /// - Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken = default); - - /// - /// Writes the to-be-committed events provided to the underlying persistence mechanism. - /// - /// The series of events and associated metadata to be committed. - /// The token to monitor for cancellation requests. - /// - /// - /// - /// - /// This interface returns a nullable ICommit object because it's implemented by - /// that can return null if the pipeline hooks decide to abort the commit. - /// - Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/NEventStore/IEventStream.cs b/src/NEventStore/IEventStream.cs deleted file mode 100644 index c4132e279..000000000 --- a/src/NEventStore/IEventStream.cs +++ /dev/null @@ -1,87 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Indicates the ability to track a series of events and commit them to durable storage. - /// - /// - /// Instances of this class are single threaded and should not be shared between threads. - /// - public interface IEventStream : IDisposable - { - /// - /// Gets the value which identifies bucket to which the stream belongs. - /// - string BucketId { get; } - - /// - /// Gets the value which uniquely identifies the stream to which the stream belongs. - /// - string StreamId { get; } - - /// - /// Gets the value which indicates the most recent committed revision of event stream. - /// - int StreamRevision { get; } - - /// - /// Gets the value which indicates the most recent committed sequence identifier of the event stream. - /// - int CommitSequence { get; } - - /// - /// Gets the collection of events which have been successfully persisted to durable storage. - /// - ICollection CommittedEvents { get; } - - /// - /// Gets the collection of committed headers associated with the stream. - /// - IDictionary CommittedHeaders { get; } - - /// - /// Gets the collection of yet-to-be-committed events that have not yet been persisted to durable storage. - /// - ICollection UncommittedEvents { get; } - - /// - /// Gets the collection of yet-to-be-committed headers associated with the uncommitted events. - /// - IDictionary UncommittedHeaders { get; } - - /// - /// Adds the event messages provided to the session to be tracked. - /// - /// The event to be tracked. - void Add(EventMessage uncommittedEvent); - - /// - /// Commits the changes to durable storage. - /// - /// The value which uniquely identifies the commit. - /// The commit which has been persisted to durable storage. - /// - /// - /// - /// - ICommit? CommitChanges(Guid commitId); - - /// - /// Commits the changes to durable storage. - /// - /// The value which uniquely identifies the commit. - /// The token to monitor for cancellation requests. - /// The commit which has been persisted to durable storage. - /// - /// - /// - /// - Task CommitChangesAsync(Guid commitId, CancellationToken cancellationToken = default); - - /// - /// Clears the uncommitted changes. - /// - void ClearChanges(); - } -} \ No newline at end of file diff --git a/src/NEventStore/IPipelineHook.cs b/src/NEventStore/IPipelineHook.cs deleted file mode 100644 index b85dd3a24..000000000 --- a/src/NEventStore/IPipelineHook.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace NEventStore -{ - /// - /// Provides the ability to hook into the pipeline of persisting a commit. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPipelineHook : IDisposable - { - /// - /// Hooks into the selection pipeline just prior to the commit being returned to the caller. - /// - /// The commit to be filtered. - /// If successful, returns a populated commit; otherwise returns null. - ICommit? SelectCommit(ICommit committed); - - /// - /// Hooks into the commit pipeline prior to persisting the commit to durable storage. - /// - /// The attempt to be committed. - /// If processing should continue, returns true; otherwise returns false. - bool PreCommit(CommitAttempt attempt); - - /// - /// Hooks into the commit pipeline just after the commit has been *successfully* committed to durable storage. - /// - /// The commit which has been persisted. - void PostCommit(ICommit committed); - - /// - /// Invoked when a bucket has been purged. If buckedId is null, then all buckets have been purged. - /// - /// The bucket Id that has been purged. Null when all buckets have been purged. - void OnPurge(string? bucketId); - - /// - /// Invoked when a stream has been deleted. - /// - /// The bucket Id from which the stream which has been deleted. - /// The stream Id of the stream which has been deleted. - void OnDeleteStream(string bucketId, string streamId); - } -} \ No newline at end of file diff --git a/src/NEventStore/IPipelineHookAsync.cs b/src/NEventStore/IPipelineHookAsync.cs deleted file mode 100644 index 601d273b1..000000000 --- a/src/NEventStore/IPipelineHookAsync.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace NEventStore -{ - /// - /// Provides the ability to hook into the pipeline of persisting a commit. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPipelineHookAsync : IDisposable - { - /// - /// Hooks into the selection pipeline just prior to the commit being returned to the caller. - /// - /// The commit to be filtered. - /// The token to monitor for cancellation requests. - /// If successful, returns a populated commit; otherwise returns null. - Task SelectCommitAsync(ICommit committed, CancellationToken cancellationToken); - - /// - /// Hooks into the commit pipeline prior to persisting the commit to durable storage. - /// - /// The attempt to be committed. - /// The token to monitor for cancellation requests. - /// If processing should continue, returns true; otherwise returns false. - Task PreCommitAsync(CommitAttempt attempt, CancellationToken cancellationToken); - - /// - /// Hooks into the commit pipeline just after the commit has been *successfully* committed to durable storage. - /// - /// The commit which has been persisted. - /// The token to monitor for cancellation requests. - Task PostCommitAsync(ICommit committed, CancellationToken cancellationToken); - - /// - /// Invoked when a bucket has been purged. If buckedId is null, then all buckets have been purged. - /// - /// The bucket Id that has been purged. Null when all buckets have been purged. - /// The token to monitor for cancellation requests. - Task OnPurgeAsync(string? bucketId, CancellationToken cancellationToken); - - /// - /// Invoked when a stream has been deleted. - /// - /// The bucket Id from which the stream which has been deleted. - /// The stream Id of the stream which has been deleted. - /// The token to monitor for cancellation requests. - Task OnDeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken); - } -} \ No newline at end of file diff --git a/src/NEventStore/ISnapshot.cs b/src/NEventStore/ISnapshot.cs deleted file mode 100644 index 178f0bc70..000000000 --- a/src/NEventStore/ISnapshot.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace NEventStore -{ - /// - /// Represents a materialized view of a stream at specific revision. - /// - public interface ISnapshot - { - /// - /// Gets the value which uniquely identifies the bucket to which the snapshot applies. - /// - string BucketId { get; } - - /// - /// Gets the value which uniquely identifies the stream to which the snapshot applies. - /// - string StreamId { get; } - - /// - /// Gets the position at which the snapshot applies. - /// - int StreamRevision { get; } - - /// - /// Gets the snapshot or materialized view of the stream at the revision indicated. - /// - object Payload { get; } - } -} \ No newline at end of file diff --git a/src/NEventStore/IStoreEvents.cs b/src/NEventStore/IStoreEvents.cs deleted file mode 100644 index db4c27b5c..000000000 --- a/src/NEventStore/IStoreEvents.cs +++ /dev/null @@ -1,76 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Indicates the ability to store and retrieve a stream of events. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IStoreEvents : IDisposable - { - /// - /// Gets a reference to the underlying persistence engine which allows direct access to persistence operations. - /// - IPersistStreams Advanced { get; } - - /// - /// Creates a new stream. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The value which uniquely identifies the stream within the bucket to be created. - /// An empty stream. - IEventStream CreateStream(string bucketId, string streamId); - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The value which uniquely identifies the stream in the bucket from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events represented as a stream. - /// - /// - /// - IEventStream OpenStream(string bucketId, string streamId, int minRevision, int maxRevision); - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The value which uniquely identifies the stream in the bucket from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// The token to monitor for cancellation requests. - /// A series of committed events represented as a stream. - /// - /// - /// - Task OpenStreamAsync(string bucketId, string streamId, int minRevision, int maxRevision, CancellationToken cancellationToken = default); - - /// - /// Reads the stream indicated from the point of the snapshot forward until the maximum revision specified. - /// - /// The snapshot of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events represented as a stream. - /// - /// - IEventStream OpenStream(ISnapshot snapshot, int maxRevision); - - /// - /// Reads the stream indicated from the point of the snapshot forward until the maximum revision specified. - /// - /// The snapshot of the stream to be read. - /// The maximum revision of the stream to be read. - /// The token to monitor for cancellation requests. - /// A series of committed events represented as a stream. - /// - /// - Task OpenStreamAsync(ISnapshot snapshot, int maxRevision, CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/NEventStore/ImmutableCollection.cs b/src/NEventStore/ImmutableCollection.cs deleted file mode 100644 index 5da4f6fe5..000000000 --- a/src/NEventStore/ImmutableCollection.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System.Collections; - -namespace NEventStore -{ - internal sealed class ImmutableCollection : ICollection, ICollection - { - private readonly ICollection _inner; - - public ImmutableCollection(ICollection inner) - { - _inner = inner; - } - - public object SyncRoot { get; } = new object(); - - public bool IsSynchronized - { - get { return false; } - } - - public void CopyTo(Array array, int index) - { - if (array == null) - { - throw new ArgumentNullException(nameof(array)); - } - - if (array.Rank != 1) - { - throw new ArgumentException("Only single dimensional arrays are supported.", nameof(array)); - } - - if (array.GetLowerBound(0) != 0) - { - throw new ArgumentException("Arrays with non-zero lower bounds are not supported.", nameof(array)); - } - - if ((uint)index > (uint)array.Length) - { - throw new ArgumentOutOfRangeException(nameof(index)); - } - - if (array.Length - index < _inner.Count) - { - throw new ArgumentException("The destination array does not have enough space.", nameof(array)); - } - - if (array is T[] typedArray) - { - // Calls through the non-generic ICollection interface can still pass a T[]. - // Keep those on the typed CopyTo path instead of using Array.SetValue per item. - _inner.CopyTo(typedArray, index); - return; - } - - // ICollection.CopyTo accepts non-generic arrays such as object[]. The previous - // implementation used Cast().ToArray(), which allocated a temporary array and - // populated that temporary instead of the caller-provided destination. SetValue keeps - // the compatibility surface while copying directly into the requested array. - foreach (var item in _inner) - { - array.SetValue(item, index++); - } - } - - public int Count - { - get { return _inner.Count; } - } - - public bool IsReadOnly - { - get { return true; } - } - - public IEnumerator GetEnumerator() - { - return _inner.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public void Add(T item) - { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - public bool Remove(T item) - { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - public void Clear() - { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - public bool Contains(T item) - { - return _inner.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - _inner.CopyTo(array, arrayIndex); - } - } -} diff --git a/src/NEventStore/ImmutableDictionary.cs b/src/NEventStore/ImmutableDictionary.cs deleted file mode 100644 index 894affa90..000000000 --- a/src/NEventStore/ImmutableDictionary.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System.Collections; -using System.Diagnostics.CodeAnalysis; - -namespace NEventStore { - /// - /// Represents an immutable dictionary. - /// - public sealed class ImmutableDictionary : IDictionary { - private readonly IDictionary _inner; - - /// - /// Initializes a new instance of the ImmutableDictionary class. - /// - public ImmutableDictionary(IDictionary inner) { - _inner = inner; - } - - /// - /// Gets the value associated with the specified key. - /// - /// - public TValue this[TKey key] { get => _inner[key]; set => throw new NotSupportedException(Resources.ReadOnlyCollection); } - - /// - /// Gets the keys in the dictionary. - /// - public ICollection Keys => _inner.Keys; - - /// - /// Gets the values in the dictionary. - /// - public ICollection Values => _inner.Values; - - /// - /// Gets the number of elements in the dictionary. - /// - public int Count => _inner.Count; - - /// - /// Gets a value indicating whether the dictionary is read-only. - /// - public bool IsReadOnly => true; - - /// - /// Adds an element with the provided key and value to the dictionary. - /// - /// - public void Add(TKey key, TValue value) { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - /// - /// Adds an item to the dictionary. - /// - /// - public void Add(KeyValuePair item) { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - /// - /// Removes all items from the dictionary. - /// - /// - public void Clear() { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - /// - /// Determines whether the dictionary contains a specific value. - /// - public bool Contains(KeyValuePair item) { - return _inner.Contains(item); - } - - /// - /// Determines whether the dictionary contains a specific key. - /// - public bool ContainsKey(TKey key) { - return _inner.ContainsKey(key); - } - - /// - /// Copies the elements of the dictionary to an array, starting at a particular array index. - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) { - _inner.CopyTo(array, arrayIndex); - } - - /// - /// Removes the element with the specified key from the dictionary. - /// - /// - public bool Remove(TKey key) { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - /// - /// Removes the first occurrence of a specific object from the dictionary. - /// - /// - public bool Remove(KeyValuePair item) { - throw new NotSupportedException(Resources.ReadOnlyCollection); - } - - /// - /// Gets the value associated with the specified key. - /// -#if NET8_0_OR_GREATER - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) -#else - public bool TryGetValue(TKey key, out TValue value) -#endif - { - return _inner.TryGetValue(key, out value); - } - - /// - /// Returns an enumerator that iterates through the dictionary. - /// - public IEnumerator> GetEnumerator() { - return _inner.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() { - return GetEnumerator(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/LambdaAsyncObserver.cs b/src/NEventStore/LambdaAsyncObserver.cs deleted file mode 100644 index 4dcff26ea..000000000 --- a/src/NEventStore/LambdaAsyncObserver.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace NEventStore -{ - /// - /// Represents an async observer that can receive notifications of commits. - /// - public class LambdaAsyncObserver : IAsyncObserver - { - private readonly Func> _onNextAsync; - private readonly Func? _onErrorAsync; - private readonly Func? _onCompletedAsync; - - /// - /// Initializes a new instance of the LambdaAsyncObserver class. - /// - /// - public LambdaAsyncObserver( - Func> onNextAsync, - Func? onErrorAsync = null, - Func? onCompletedAsync = null) - { - _onNextAsync = onNextAsync ?? throw new ArgumentNullException(nameof(onNextAsync)); - _onErrorAsync = onErrorAsync; - _onCompletedAsync = onCompletedAsync; - } - - /// - public Task OnCompletedAsync(CancellationToken cancellationToken) - { - return _onCompletedAsync?.Invoke(cancellationToken) ?? Task.CompletedTask; - } - - /// - public Task OnErrorAsync(Exception ex, CancellationToken cancellationToken) - { - return _onErrorAsync?.Invoke(ex, cancellationToken) ?? Task.CompletedTask; - } - - /// - public Task OnNextAsync(T value, CancellationToken cancellationToken) - { - return _onNextAsync(value, cancellationToken); - } - } -} diff --git a/src/NEventStore/Logging/LogFactory.cs b/src/NEventStore/Logging/LogFactory.cs deleted file mode 100644 index 630e4965b..000000000 --- a/src/NEventStore/Logging/LogFactory.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace NEventStore.Logging -{ - /// - /// Provides the ability to get a new instance of the configured logger. - /// - public static class LogFactory - { - /// - /// Initializes static members of the LogFactory class. - /// - static LogFactory() - { - BuildLogger = _ => NullLogger.Instance; - } - - /// - /// Gets or sets the log builder of the configured logger. - /// This should be invoked to return a new logging instance. - /// - public static Func BuildLogger { get; set; } - } -} \ No newline at end of file diff --git a/src/NEventStore/LoggingWireupExtensions.cs b/src/NEventStore/LoggingWireupExtensions.cs deleted file mode 100644 index e13c4ad80..000000000 --- a/src/NEventStore/LoggingWireupExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore -{ - /// - /// Provides extension methods to configure logging. - /// - public static class LoggingWireupExtensions - { - /// - /// Configures NEventStore to use the specified logger factory. - /// - public static Wireup WithLoggerFactory(this Wireup wireup, ILoggerFactory loggerFactory) - { - return wireup.LogTo(type => loggerFactory.CreateLogger(type)); - } - - /// - /// Configures NEventStore to use the specified logger. - /// - public static Wireup LogTo(this Wireup wireup, Func logger) - { - LogFactory.BuildLogger = logger; - return wireup; - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Messages.Designer.cs b/src/NEventStore/Messages.Designer.cs deleted file mode 100644 index 1bbad3627..000000000 --- a/src/NEventStore/Messages.Designer.cs +++ /dev/null @@ -1,396 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NEventStore { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Adding wireup registration callback.. - /// - internal static string AddingWireupCallback { - get { - return ResourceManager.GetString("AddingWireupCallback", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Adding wireup registration for an object instance of type '{0}'.. - /// - internal static string AddingWireupRegistration { - get { - return ResourceManager.GetString("AddingWireupRegistration", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to resolve existing instance.. - /// - internal static string AttemptingToResolveInstance { - get { - return ResourceManager.GetString("AttemptingToResolveInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring SQL engine to auto-detect dialect.. - /// - internal static string AutoDetectDialect { - get { - return ResourceManager.GetString("AutoDetectDialect", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Building (and storing) new instance for later calls.. - /// - internal static string BuildingAndStoringNewInstance { - get { - return ResourceManager.GetString("BuildingAndStoringNewInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Building the persistence engine.. - /// - internal static string BuildingEngine { - get { - return ResourceManager.GetString("BuildingEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Building new instance.. - /// - internal static string BuildingNewInstance { - get { - return ResourceManager.GetString("BuildingNewInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Head CommitSequence [{0}] greater or equal than Attempt BucketId [{1}] - CommitSequence [{2}] - StreamId {3} - StreamRevision {4} - Events Count {5}. - /// - internal static string ConcurrencyExceptionCommitSequence { - get { - return ResourceManager.GetString("ConcurrencyExceptionCommitSequence", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Head StreamRevision [{0}] greater or equal than Attempt BucketId [{1}] - StreamId {2} - StreamRevision {3} - Events Count {4}. - /// - internal static string ConcurrencyExceptionStreamRevision { - get { - return ResourceManager.GetString("ConcurrencyExceptionStreamRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring serializer to compress the serialized payload.. - /// - internal static string ConfiguringCompression { - get { - return ResourceManager.GetString("ConfiguringCompression", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring serializer to encrypt the serialized payload.. - /// - internal static string ConfiguringEncryption { - get { - return ResourceManager.GetString("ConfiguringEncryption", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring persistence engine to enlist in ambient transactions using TransactionScope.. - /// - internal static string ConfiguringEngineEnlistment { - get { - return ResourceManager.GetString("ConfiguringEngineEnlistment", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring persistence engine to initialize.. - /// - internal static string ConfiguringEngineInitialization { - get { - return ResourceManager.GetString("ConfiguringEngineInitialization", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring persistence engine to track performance. - /// - internal static string ConfiguringEnginePerformanceTracking { - get { - return ResourceManager.GetString("ConfiguringEnginePerformanceTracking", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registration configured to resolve a new instance per call.. - /// - internal static string ConfiguringInstancePerCall { - get { - return ResourceManager.GetString("ConfiguringInstancePerCall", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using SQL connection factory of type '{0}'.. - /// - internal static string ConnectionFactorySpecified { - get { - return ResourceManager.GetString("ConnectionFactorySpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering SQL dialect of type '{0}'.. - /// - internal static string DialectSpecified { - get { - return ResourceManager.GetString("DialectSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Stream: {0} Bucket: {1} Duplicate commit id {2}.. - /// - internal static string DuplicateCommitIdException { - get { - return ResourceManager.GetString("DuplicateCommitIdException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuring the store to upconvert events when fetched.. - /// - internal static string EventUpconverterRegistered { - get { - return ResourceManager.GetString("EventUpconverterRegistered", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Will scan for event upconverters from the following assemblies: '{0}'. - /// - internal static string EventUpconvertersLoadedFrom { - get { - return ResourceManager.GetString("EventUpconvertersLoadedFrom", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot only compare {0} with {1}.. - /// - internal static string FailedToCompareCheckpoint { - get { - return ResourceManager.GetString("FailedToCompareCheckpoint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing the configured persistence engine.. - /// - internal static string InitializingEngine { - get { - return ResourceManager.GetString("InitializingEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The instance provided cannot be null.. - /// - internal static string InstanceCannotBeNull { - get { - return ResourceManager.GetString("InstanceCannotBeNull", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Persistence engine configured to page every '{0}' records.. - /// - internal static string PagingSpecified { - get { - return ResourceManager.GetString("PagingSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering persistence engine of type '{0}'.. - /// - internal static string RegisteringPersistenceEngine { - get { - return ResourceManager.GetString("RegisteringPersistenceEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering wireup instance for service of type '{0}'.. - /// - internal static string RegisteringServiceInstance { - get { - return ResourceManager.GetString("RegisteringServiceInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering wireup resolver for service of type '{0}'.. - /// - internal static string RegisteringWireupCallback { - get { - return ResourceManager.GetString("RegisteringWireupCallback", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Resolving instance.. - /// - internal static string ResolvingInstance { - get { - return ResourceManager.GetString("ResolvingInstance", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to resolve instance for service of type '{0}'.. - /// - internal static string ResolvingService { - get { - return ResourceManager.GetString("ResolvingService", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Head CommitSequence [{0}] lesser than Attempt BucketId [{1}] - CommitSequence [{2}] - StreamId {3} - StreamRevision {4} - Events Count {5}. - /// - internal static string StorageExceptionCommitSequence { - get { - return ResourceManager.GetString("StorageExceptionCommitSequence", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Stream EOF: head.StreamRevision [{0}] < attempt.StreamRevision [{1}] - attempt.Events.Count [{2}] - BucketId {3} - StreamId {4} - StreamRevision {5} . - /// - internal static string StorageExceptionEndOfStream { - get { - return ResourceManager.GetString("StorageExceptionEndOfStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registering stream ID hasher of type '{0}'. - /// - internal static string StreamIdHasherSpecified { - get { - return ResourceManager.GetString("StreamIdHasherSpecified", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Stream not found: StreamId {0} bucket {1}.. - /// - internal static string StreamNotFoundException { - get { - return ResourceManager.GetString("StreamNotFoundException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type provided must be registered as an interface rather than as a concrete type, e.g. "container.Register<ISomeService>(instance);".. - /// - internal static string TypeMustBeInterface { - get { - return ResourceManager.GetString("TypeMustBeInterface", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to resolve requested instance of type '{0}'.. - /// - internal static string UnableToResolve { - get { - return ResourceManager.GetString("UnableToResolve", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wrapping serializer of type '{0}' in RijndaelSerializer.. - /// - internal static string WrappingSerializerEncryption { - get { - return ResourceManager.GetString("WrappingSerializerEncryption", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Wrapping serializer of type '{0}' in GZipSerializer.. - /// - internal static string WrappingSerializerGZip { - get { - return ResourceManager.GetString("WrappingSerializerGZip", resourceCulture); - } - } - } -} diff --git a/src/NEventStore/Messages.resx b/src/NEventStore/Messages.resx deleted file mode 100644 index eef77689c..000000000 --- a/src/NEventStore/Messages.resx +++ /dev/null @@ -1,231 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The instance provided cannot be null. - - - The type provided must be registered as an interface rather than as a concrete type, e.g. "container.Register<ISomeService>(instance);". - - - Adding wireup registration for an object instance of type '{0}'. - - - Adding wireup registration callback. - - - Registration configured to resolve a new instance per call. - - - Resolving instance. - - - Building new instance. - - - Attempting to resolve existing instance. - - - Building (and storing) new instance for later calls. - - - Registering wireup resolver for service of type '{0}'. - - - Registering wireup instance for service of type '{0}'. - - - Attempting to resolve instance for service of type '{0}'. - - - Unable to resolve requested instance of type '{0}'. - - - Registering persistence engine of type '{0}'. - - - Configuring persistence engine to initialize. - - - Configuring persistence engine to enlist in ambient transactions using TransactionScope. - - - Building the persistence engine. - - - Initializing the configured persistence engine. - - - Configuring serializer to compress the serialized payload. - - - Wrapping serializer of type '{0}' in GZipSerializer. - - - Configuring serializer to encrypt the serialized payload. - - - Wrapping serializer of type '{0}' in RijndaelSerializer. - - - Configuring SQL engine to auto-detect dialect. - - - Persistence engine configured to page every '{0}' records. - - - Registering SQL dialect of type '{0}'. - - - Using SQL connection factory of type '{0}'. - - - Configuring the store to upconvert events when fetched. - - - Will scan for event upconverters from the following assemblies: '{0}' - - - Configuring persistence engine to track performance - - - Cannot only compare {0} with {1}. - - - Registering stream ID hasher of type '{0}' - - - Head CommitSequence [{0}] greater or equal than Attempt BucketId [{1}] - CommitSequence [{2}] - StreamId {3} - StreamRevision {4} - Events Count {5} - - - Head StreamRevision [{0}] greater or equal than Attempt BucketId [{1}] - StreamId {2} - StreamRevision {3} - Events Count {4} - - - Stream: {0} Bucket: {1} Duplicate commit id {2}. - - - Head CommitSequence [{0}] lesser than Attempt BucketId [{1}] - CommitSequence [{2}] - StreamId {3} - StreamRevision {4} - Events Count {5} - - - Stream EOF: head.StreamRevision [{0}] < attempt.StreamRevision [{1}] - attempt.Events.Count [{2}] - BucketId {3} - StreamId {4} - StreamRevision {5} - - - Stream not found: StreamId {0} bucket {1}. - - \ No newline at end of file diff --git a/src/NEventStore/NEventStore.Core.csproj b/src/NEventStore/NEventStore.Core.csproj deleted file mode 100644 index da119fb51..000000000 --- a/src/NEventStore/NEventStore.Core.csproj +++ /dev/null @@ -1,95 +0,0 @@ - - - netstandard2.0;net462;$(ModernTargetFrameworks) - false - NEventStore - NEventStore - false - TRACE;DEBUG - - - NEventStore - NEventStore - NEventStore Dev Team - http://neventstore.org - false - The purpose of the EventStore is to represent a series of events as a stream. NEventStore is a persistence agnostic event sourcing library for .NET. The primary use is most often associated with CQRS. - events, event sourcing, cqrs, storage, persistence, database - - True - true - true - snupkg - true - true - True - True - NEventStore Dev Team - icon.png - Readme.md - https://github.com/NEventStore/NEventStore - git - license.txt - True - True - latest-recommended - - - - - - - - - True - \ - - - True - \ - - - True - \ - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - True - True - Messages.resx - - - True - True - Resources.resx - - - True - True - Messages.resx - - - - - ResXFileCodeGenerator - Messages.Designer.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - - - ResXFileCodeGenerator - Messages.Designer.cs - - - diff --git a/src/NEventStore/NanoContainer.cs b/src/NEventStore/NanoContainer.cs deleted file mode 100644 index cd63216ba..000000000 --- a/src/NEventStore/NanoContainer.cs +++ /dev/null @@ -1,175 +0,0 @@ -using NEventStore.Logging; -using Microsoft.Extensions.Logging; - -namespace NEventStore -{ - /// - /// Represents a simple IoC container. - /// - public class NanoContainer - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(NanoContainer)); - - private readonly Dictionary _registrations = []; - - /// - /// Registers a service with the container. - /// - public virtual ContainerRegistration Register(Func resolve) - where TService : class - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.RegisteringWireupCallback, typeof(TService)); - } - var registration = new ContainerRegistration(c => resolve(c)); - _registrations[typeof(TService)] = registration; - return registration; - } - - /// - /// Registers a service instance with the container. - /// - /// - /// - public virtual ContainerRegistration Register(TService instance) - { - if (Equals(instance, null)) - { - throw new ArgumentNullException(nameof(instance), Messages.InstanceCannotBeNull); - } - - if (!typeof(TService).IsValueType && !typeof(TService).IsInterface) - { - throw new ArgumentException(Messages.TypeMustBeInterface, nameof(instance)); - } - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.RegisteringServiceInstance, typeof(TService)); - } - var registration = new ContainerRegistration(instance); - _registrations[typeof(TService)] = registration; - return registration; - } - - /// - /// Resolves a service from the container. - /// - public virtual TService? Resolve() - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.ResolvingService, typeof(TService)); - } - - if (_registrations.TryGetValue(typeof(TService), out ContainerRegistration? registration)) - { - var obj = registration.Resolve(this); - return obj != null ? (TService)obj : default; - } - - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.UnableToResolve, typeof(TService)); - } - return default; - } - } - - /// - /// Represents a registration in the container. - /// - public class ContainerRegistration - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(ContainerRegistration)); - private readonly Func? _resolve; - private object? _instance; - private bool _instancePerCall; - - /// - /// Initializes a new instance of the ContainerRegistration class. - /// - public ContainerRegistration(Func resolve) - { - if (resolve is null) - { - throw new ArgumentNullException(nameof(resolve)); - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.AddingWireupCallback); - } - _resolve = resolve; - } - - /// - /// Initializes a new instance of the ContainerRegistration class. - /// - public ContainerRegistration(object instance) - { - if (instance is null) - { - throw new ArgumentNullException(nameof(instance)); - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.AddingWireupRegistration, instance.GetType()); - } - _instance = instance; - } - - /// - /// Configures the registration to be resolved once per call. - /// - public virtual ContainerRegistration InstancePerCall() - { - if (_resolve == null) - { - throw new InvalidOperationException("Cannot configure InstancePerCall() on instance registrations."); - } - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.ConfiguringInstancePerCall); - } - _instancePerCall = true; - return this; - } - - /// - /// Resolves the registration. - /// - public virtual object? Resolve(NanoContainer container) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.ResolvingInstance); - } - if (_instancePerCall) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.BuildingNewInstance); - } - return _resolve!(container); - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.AttemptingToResolveInstance); - } - - if (_instance != null) - { - return _instance; - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.BuildingAndStoringNewInstance); - } - return _instance = _resolve!(container); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/OptimisticEventStore.cs b/src/NEventStore/OptimisticEventStore.cs deleted file mode 100644 index 6a81ed99e..000000000 --- a/src/NEventStore/OptimisticEventStore.cs +++ /dev/null @@ -1,297 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Logging; -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// An implementation of a store that supports optimistic concurrency. - /// - public class OptimisticEventStore : IStoreEvents, ICommitEvents, ICommitEventsAsync - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(OptimisticEventStore)); - private readonly IPersistStreams _persistence; - private readonly IEnumerable _pipelineHooks; - private readonly IEnumerable _pipelineHooksAsync; - - /// - public virtual IPersistStreams Advanced { get => _persistence; } - - /// - /// Initializes a new instance of the OptimisticEventStore class. - /// - /// - public OptimisticEventStore(IPersistStreams persistence, IEnumerable? pipelineHooks, IEnumerable? pipelineHooksAsync) - { - if (persistence == null) - { - throw new ArgumentNullException(nameof(persistence)); - } - - _pipelineHooks = pipelineHooks ?? []; - _pipelineHooksAsync = pipelineHooksAsync ?? []; - if (_pipelineHooks.Any() || _pipelineHooksAsync.Any()) - { - _persistence = new PipelineHooksAwarePersistStreamsDecorator(persistence, _pipelineHooks, _pipelineHooksAsync); - } - else - { - _persistence = persistence; - } - } - - /// - public virtual IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision) - { - return _persistence.GetFrom(bucketId, streamId, minRevision, maxRevision); - } - - /// - /// - /// Pipeline hooks can prevent commits to be persisted by returning false from the PreCommit method. - /// If the pipeline hook prevents the commit from being persisted, the commit will not be persisted and null will be returned. - /// - public virtual ICommit? Commit(CommitAttempt attempt) - { - Guard.NotNull(() => attempt, attempt); - foreach (var hook in _pipelineHooks) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); - } - if (hook.PreCommit(attempt)) - { - continue; - } - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); - } - return null; - } - foreach (var hook in _pipelineHooksAsync) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); - } - if (hook.PreCommitAsync(attempt, CancellationToken.None).GetAwaiter().GetResult()) - { - continue; - } - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); - } - return null; - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.CommittingAttempt, attempt.CommitId, attempt.Events?.Count ?? 0); - } - var commit = _persistence.Commit(attempt); - - if (commit != null) - { - foreach (var hook in _pipelineHooks) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); - } - hook.PostCommit(commit); - } - foreach (var hook in _pipelineHooksAsync) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); - } - hook.PostCommitAsync(commit, CancellationToken.None).GetAwaiter().GetResult(); - } - } - return commit; - } - - /// - public virtual Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken) - { - return _persistence.GetFromAsync(bucketId, streamId, minRevision, maxRevision, observer, cancellationToken); - } - - /// - /// - /// Pipeline hooks can prevent commits to be persisted by returning false from the PreCommit method. - /// If the pipeline hook prevents the commit from being persisted, the commit will not be persisted and null will be returned. - /// - public async Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) - { - Guard.NotNull(() => attempt, attempt); - foreach (var hook in _pipelineHooks) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); - } - if (hook.PreCommit(attempt)) - { - continue; - } - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); - } - return null; - } - foreach (var hook in _pipelineHooksAsync) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.InvokingPreCommitHooks, attempt.CommitId, hook.GetType()); - } - if (await hook.PreCommitAsync(attempt, cancellationToken).ConfigureAwait(false)) - { - continue; - } - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.CommitRejectedByPipelineHook, hook.GetType(), attempt.CommitId); - } - return null; - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.CommittingAttempt, attempt.CommitId, attempt.Events?.Count ?? 0); - } - var commit = await _persistence.CommitAsync(attempt, cancellationToken).ConfigureAwait(false); - - if (commit != null) - { - foreach (var hook in _pipelineHooks) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); - } - hook.PostCommit(commit); - } - foreach (var hook in _pipelineHooksAsync) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.InvokingPostCommitPipelineHooks, attempt.CommitId, hook.GetType()); - } - await hook.PostCommitAsync(commit, cancellationToken).ConfigureAwait(false); - } - } - return commit; - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - public virtual IEventStream CreateStream(string bucketId, string streamId) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.CreatingStream, streamId, bucketId); - } - return new OptimisticEventStream(bucketId, streamId, this, this); - } - - /// - public virtual IEventStream OpenStream(string bucketId, string streamId, int minRevision, int maxRevision) - { - maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.OpeningStreamAtRevision, streamId, bucketId, minRevision, maxRevision); - } - var stream = new OptimisticEventStream(bucketId, streamId, this, this); - stream.Initialize(minRevision, maxRevision); - return stream; - } - - /// - public async Task OpenStreamAsync(string bucketId, string streamId, int minRevision, int maxRevision, CancellationToken cancellationToken) - { - maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.OpeningStreamAtRevision, streamId, bucketId, minRevision, maxRevision); - } - var stream = new OptimisticEventStream(bucketId, streamId, this, this); - await stream.InitializeAsync(minRevision, maxRevision, cancellationToken).ConfigureAwait(false); - return stream; - } - - /// - public virtual IEventStream OpenStream(ISnapshot snapshot, int maxRevision) - { - if (snapshot == null) - { - throw new ArgumentNullException(nameof(snapshot)); - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.OpeningStreamWithSnapshot, snapshot.StreamId, snapshot.BucketId, snapshot.StreamRevision, maxRevision); - } - maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; - var stream = new OptimisticEventStream(snapshot.BucketId, snapshot.StreamId, this, this); - stream.Initialize(snapshot, maxRevision); - return stream; - } - - /// - public async Task OpenStreamAsync(ISnapshot snapshot, int maxRevision, CancellationToken cancellationToken) - { - if (snapshot == null) - { - throw new ArgumentNullException(nameof(snapshot)); - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.OpeningStreamWithSnapshot, snapshot.StreamId, snapshot.BucketId, snapshot.StreamRevision, maxRevision); - } - maxRevision = maxRevision <= 0 ? int.MaxValue : maxRevision; - var stream = new OptimisticEventStream(snapshot.BucketId, snapshot.StreamId, this, this); - await stream.InitializeAsync(snapshot, maxRevision, cancellationToken).ConfigureAwait(false); - return stream; - } - - /// - protected virtual void Dispose(bool disposing) - { - if (!disposing) - { - return; - } - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.ShuttingDownStore); - } - _persistence.Dispose(); - foreach (var hook in _pipelineHooks) - { - hook.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/src/NEventStore/OptimisticEventStream.cs b/src/NEventStore/OptimisticEventStream.cs deleted file mode 100644 index 1f38a93e8..000000000 --- a/src/NEventStore/OptimisticEventStream.cs +++ /dev/null @@ -1,587 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore -{ - /// - /// Represents a stream of events that can be committed and appended to. - /// - [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", - Justification = "This behaves like a stream--not a .NET 'Stream' object, but a stream nonetheless.")] - public sealed class OptimisticEventStream : IEventStream - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(OptimisticEventStream)); - // This stream mostly appends events, counts them, and copies them into arrays when building commit - // attempts. A contiguous List matches that access pattern better than a linked list because it - // avoids one allocation per node and makes the eventual CopyTo path cheaper. The public contract - // still stays ICollection through the immutable wrappers, so callers do not observe - // the storage swap. - private readonly List _committed = []; - private readonly ImmutableCollection _committedImmutableWrapper; - private readonly Dictionary _committedHeaders = []; - private readonly ImmutableDictionary _committedHeadersImmutableWrapper; - private readonly List _events = []; - private readonly ImmutableCollection _eventsImmutableWrapper; - // Duplicate commit identifiers are enforced at the persistence boundary rather than inside - // this stream instance. A stream can be reopened from any revision range, so the commits - // loaded into this object are not guaranteed to be the complete history for the stream. - // Keeping a local cache of loaded CommitIds would therefore only reject the subset of - // duplicates that happened to be observed by this instance and would miss duplicates from - // earlier unseen commits. The persistence engine is the only component that can validate - // the full BucketId + StreamId + CommitId identity across reopened streams and concurrent - // writers, so the stream delegates that invariant there. - private readonly ICommitEvents _persistence; - private readonly ICommitEventsAsync _persistenceAsync; - private bool _disposed; - // a stream is considered partial if we haven't read all the events in a commit - private bool _isPartialStream; - - /// - public string BucketId { get; } - /// - public string StreamId { get; } - /// - public int StreamRevision { get; private set; } - /// - public int CommitSequence { get; private set; } - /// - public ICollection CommittedEvents { get => _committedImmutableWrapper; } - /// - public IDictionary CommittedHeaders { get => _committedHeadersImmutableWrapper; } - /// - public ICollection UncommittedEvents { get => _eventsImmutableWrapper; } - /// - public IDictionary UncommittedHeaders { get; } = new Dictionary(); - - /// - /// Create a new instance of the OptimisticEventStream class. - /// - public OptimisticEventStream(string bucketId, string streamId, ICommitEvents persistence, ICommitEventsAsync persistenceAsync) - { - if (string.IsNullOrWhiteSpace(bucketId)) - { - throw new ArgumentException($"'{nameof(bucketId)}' cannot be null or whitespace.", nameof(bucketId)); - } - - if (string.IsNullOrWhiteSpace(streamId)) - { - throw new ArgumentException($"'{nameof(streamId)}' cannot be null or whitespace.", nameof(streamId)); - } - - BucketId = bucketId; - StreamId = streamId; - _persistence = persistence ?? throw new ArgumentNullException(nameof(persistence)); - _persistenceAsync = persistenceAsync ?? throw new ArgumentNullException(nameof(persistenceAsync)); - _committedImmutableWrapper = new ImmutableCollection(_committed); - _eventsImmutableWrapper = new ImmutableCollection(_events); - _committedHeadersImmutableWrapper = new ImmutableDictionary(_committedHeaders); - } - - private void EnsureStreamIsNew() - { - if (_committed.Count > 0 || _events.Count > 0) - { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Cannot call Initialize on a stream already used: Bucked: {0}, Stream: {1}, Committed Events: {2}, New Events: {3}", BucketId, StreamId, _committed.Count, _events.Count)); - } - } - - /// - /// Initializes a new instance of the OptimisticEventStream class. - /// - /// - /// - public void Initialize(int minRevision, int maxRevision) - { - EnsureStreamIsNew(); - IEnumerable commits = _persistence.GetFrom(BucketId, StreamId, minRevision, maxRevision); - PopulateStream(minRevision, maxRevision, commits); - - if (minRevision > 0 && _committed.Count == 0) - { - throw new StreamNotFoundException(String.Format(CultureInfo.InvariantCulture, Messages.StreamNotFoundException, StreamId, BucketId)); - } - } - - /// - /// Initializes a new instance of the OptimisticEventStream class. - /// - /// - /// - public async Task InitializeAsync(int minRevision, int maxRevision, CancellationToken cancellationToken) - { - EnsureStreamIsNew(); - _isPartialStream = false; - var observer = new LambdaAsyncObserver( - onNextAsync: (commit, _) => - { - InnerPopulateStream(minRevision, maxRevision, commit); - return Task.FromResult(true); - }); - await _persistenceAsync.GetFromAsync(BucketId, StreamId, minRevision, maxRevision, observer, cancellationToken).ConfigureAwait(false); - - if (minRevision > 0 && _committed.Count == 0) - { - throw new StreamNotFoundException(String.Format(CultureInfo.InvariantCulture, Messages.StreamNotFoundException, StreamId, BucketId)); - } - } - - private void EnsureSnapshotIsForThisStream(ISnapshot snapshot) - { - if (BucketId != snapshot.BucketId || StreamId != snapshot.StreamId) - { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "The snapshot is for a different stream. Stream BucketId: {0}, StreamId: {1}; Snapshot BucketId: {2}, StreamId: {3}", BucketId, StreamId, snapshot.BucketId, snapshot.StreamId)); - } - } - - /// - /// Initializes a new instance of the OptimisticEventStream class. - /// - /// - /// - public void Initialize(ISnapshot snapshot, int maxRevision) - { - if (snapshot is null) - { - throw new ArgumentNullException(nameof(snapshot)); - } - EnsureSnapshotIsForThisStream(snapshot); - EnsureStreamIsNew(); - IEnumerable commits = _persistence.GetFrom(snapshot.BucketId, snapshot.StreamId, snapshot.StreamRevision, maxRevision); - PopulateStream(snapshot.StreamRevision + 1, maxRevision, commits); - StreamRevision = snapshot.StreamRevision + _committed.Count; - } - - /// - /// Initializes a new instance of the OptimisticEventStream class. - /// - /// - /// - public async Task InitializeAsync(ISnapshot snapshot, int maxRevision, CancellationToken cancellationToken) - { - if (snapshot is null) - { - throw new ArgumentNullException(nameof(snapshot)); - } - EnsureSnapshotIsForThisStream(snapshot); - EnsureStreamIsNew(); - int minRevision = snapshot.StreamRevision + 1; - _isPartialStream = false; - var observer = new LambdaAsyncObserver( - onNextAsync: (commit, _) => - { - InnerPopulateStream(minRevision, maxRevision, commit); - return Task.FromResult(true); - }); - await _persistenceAsync.GetFromAsync(snapshot.BucketId, snapshot.StreamId, snapshot.StreamRevision, maxRevision, observer, cancellationToken).ConfigureAwait(false); - StreamRevision = snapshot.StreamRevision + _committed.Count; - } - - /// - public void Add(EventMessage uncommittedEvent) - { - if (uncommittedEvent == null) - { - throw new ArgumentNullException(nameof(uncommittedEvent)); - } - - if (uncommittedEvent.Body == null) - { - throw new ArgumentException(nameof(uncommittedEvent.Body)); - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.AppendingUncommittedToStream, uncommittedEvent.Body.GetType(), StreamId, BucketId); - } - _events.Add(uncommittedEvent); - } - - /// - public ICommit? CommitChanges(Guid commitId) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.AttemptingToCommitChanges, StreamId, BucketId); - } - - if (_isPartialStream) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.CannotAddCommitsToPartiallyLoadedStream, StreamId, BucketId, StreamRevision); - } - - RefreshStreamAfterConcurrencyException(); - - throw new ConcurrencyException(string.Format( - CultureInfo.InvariantCulture, - Resources.CannotAddCommitsToPartiallyLoadedStream, - StreamId, - BucketId, - StreamRevision - )); - } - - if (!HasChanges()) - { - return null; - } - - try - { - return PersistChanges(commitId); - } - catch (ConcurrencyException cex) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.UnderlyingStreamHasChanged, StreamId, BucketId, cex.Message); - } - - RefreshStreamAfterConcurrencyException(); - - throw; - } - } - - /// - public async Task CommitChangesAsync(Guid commitId, CancellationToken cancellationToken) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.AttemptingToCommitChanges, StreamId, BucketId); - } - - if (_isPartialStream) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.CannotAddCommitsToPartiallyLoadedStream, StreamId, BucketId, StreamRevision); - } - - await RefreshStreamAfterConcurrencyExceptionAsync(cancellationToken).ConfigureAwait(false); - - throw new ConcurrencyException(string.Format( - CultureInfo.InvariantCulture, - Resources.CannotAddCommitsToPartiallyLoadedStream, - StreamId, - BucketId, - StreamRevision - )); - } - - if (!HasChanges()) - { - return null; - } - - try - { - return await PersistChangesAsync(commitId, cancellationToken).ConfigureAwait(false); - } - catch (ConcurrencyException cex) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.UnderlyingStreamHasChanged, StreamId, BucketId, cex.Message); - } - - await RefreshStreamAfterConcurrencyExceptionAsync(cancellationToken).ConfigureAwait(false); - - throw; - } - } - - private void RefreshStreamAfterConcurrencyException() - { - int refreshFromRevision = StreamRevision + 1; - IEnumerable commits = _persistence.GetFrom(BucketId, StreamId, refreshFromRevision, int.MaxValue); - PopulateStream(refreshFromRevision, int.MaxValue, commits); - } - - private Task RefreshStreamAfterConcurrencyExceptionAsync(CancellationToken cancellationToken) - { - int refreshFromRevision = StreamRevision + 1; - const int maxRevision = int.MaxValue; - _isPartialStream = false; - var observer = new LambdaAsyncObserver( - onNextAsync: (commit, _) => - { - InnerPopulateStream(refreshFromRevision, maxRevision, commit); - return Task.FromResult(true); - }); - return _persistenceAsync.GetFromAsync(BucketId, StreamId, refreshFromRevision, maxRevision, observer, cancellationToken); - } - - /// - public void ClearChanges() - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.ClearingUncommittedChanges, StreamId, BucketId); - } - _events.Clear(); - UncommittedHeaders.Clear(); - } - - private void PopulateStream(int minRevision, int maxRevision, ICommit commit) - { - _isPartialStream = false; - InnerPopulateStream(minRevision, maxRevision, commit); - } - - private void PopulateStream(int minRevision, int maxRevision, IEnumerable commits) - { - _isPartialStream = false; - PreSizeCommittedEventsBuffer(commits, minRevision, maxRevision); - foreach (var commit in commits ?? []) - { - InnerPopulateStream(minRevision, maxRevision, commit); - } - } - - private void InnerPopulateStream(int minRevision, int maxRevision, ICommit commit) - { - int currentRevision = commit.StreamRevision - commit.Events.Count + 1; - // just in case the persistence returned more commits than it should be - if (currentRevision > maxRevision) - { - _isPartialStream = true; - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.IgnoringBeyondRevision, commit.CommitId, StreamId, maxRevision); - } - return; - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.AddingCommitsToStream, commit.CommitId, commit.Events.Count, StreamId, BucketId); - } - - CommitSequence = commit.CommitSequence; - - CopyToCommittedHeaders(commit); - EnsureCommittedCapacityForRange(currentRevision, commit.Events.Count, minRevision, maxRevision); - CopyToEvents(minRevision, maxRevision, currentRevision, commit); - } - - private void CopyToCommittedHeaders(ICommit commit) - { - foreach (var header in commit.Headers) - { - _committedHeaders[header.Key] = header.Value; - } - } - - private void CopyToEvents(int minRevision, int maxRevision, int currentRevision, ICommit commit) - { - foreach (var @event in commit.Events) - { - if (currentRevision > maxRevision) - { - _isPartialStream = true; - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.IgnoringBeyondRevision, commit.CommitId, StreamId, maxRevision); - } - break; - } - - if (currentRevision++ < minRevision) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.IgnoringBeforeRevision, commit.CommitId, StreamId, maxRevision); - } - continue; - } - - _committed.Add(@event); - StreamRevision = currentRevision - 1; - } - } - - private void PreSizeCommittedEventsBuffer(IEnumerable commits, int minRevision, int maxRevision) - { - // We only pre-size when the persistence result is already an in-memory collection that can be - // safely enumerated twice. That keeps the optimization local to known collections such as the - // in-memory engine and test stubs without forcing deferred providers into an unexpected second - // read just to estimate capacity. - if (commits is IReadOnlyCollection readOnlyCommits) - { - EnsureCommittedCapacity(readOnlyCommits, minRevision, maxRevision); - return; - } - - if (commits is ICollection commitsCollection) - { - EnsureCommittedCapacity(commitsCollection, minRevision, maxRevision); - } - } - - private void EnsureCommittedCapacity(IEnumerable commits, int minRevision, int maxRevision) - { - var additionalEventCount = 0; - foreach (var commit in commits) - { - var firstEventRevision = commit.StreamRevision - commit.Events.Count + 1; - additionalEventCount += CountEventsInRequestedRange(firstEventRevision, commit.Events.Count, minRevision, maxRevision); - } - - if (additionalEventCount > 0) - { - SetCommittedCapacityAtLeast(_committed.Count + additionalEventCount); - } - } - - private void EnsureCommittedCapacityForRange(int firstEventRevision, int eventCount, int minRevision, int maxRevision) - { - var additionalEventCount = CountEventsInRequestedRange(firstEventRevision, eventCount, minRevision, maxRevision); - if (additionalEventCount > 0) - { - SetCommittedCapacityAtLeast(_committed.Count + additionalEventCount); - } - } - - private void SetCommittedCapacityAtLeast(int requiredCapacity) - { - // Capacity is used instead of List.EnsureCapacity so the optimization still compiles on the - // older target frameworks supported by NEventStore. The growth strategy stays geometric rather - // than setting Capacity to the exact requested size because the async read path materializes one - // commit at a time. Exact growth would force a full-array copy on nearly every append there and - // turn stream opens back into an O(n^2) allocation pattern for long streams. - if (_committed.Capacity >= requiredCapacity) - { - return; - } - - var newCapacity = _committed.Capacity == 0 ? 4 : _committed.Capacity; - while (newCapacity < requiredCapacity) - { - var doubledCapacity = newCapacity * 2; - if (doubledCapacity <= 0) - { - newCapacity = requiredCapacity; - break; - } - - newCapacity = doubledCapacity; - } - - _committed.Capacity = newCapacity; - } - - private static int CountEventsInRequestedRange(int firstEventRevision, int eventCount, int minRevision, int maxRevision) - { - if (eventCount == 0) - { - return 0; - } - - var lastEventRevision = firstEventRevision + eventCount - 1; - var firstIncludedRevision = Math.Max(firstEventRevision, minRevision); - var lastIncludedRevision = Math.Min(lastEventRevision, maxRevision); - return firstIncludedRevision > lastIncludedRevision - ? 0 - : lastIncludedRevision - firstIncludedRevision + 1; - } - - private bool HasChanges() - { - if (_disposed) - { - throw new ObjectDisposedException(Resources.AlreadyDisposed); - } - - if (_events.Count > 0) - { - return true; - } - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.NoChangesToCommit, StreamId, BucketId); - } - return false; - } - - private ICommit? PersistChanges(Guid commitId) - { - CommitAttempt attempt = BuildCommitAttempt(commitId); - - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.PersistingCommit, commitId, StreamId, BucketId, attempt.Events?.Count ?? 0); - } - var commit = _persistence.Commit(attempt); - if (commit != null) - { - PopulateStream(StreamRevision + 1, attempt.StreamRevision, commit); - ClearChanges(); - } - return commit; - } - - private async Task PersistChangesAsync(Guid commitId, CancellationToken cancellationToken) - { - CommitAttempt attempt = BuildCommitAttempt(commitId); - - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.PersistingCommit, commitId, StreamId, BucketId, attempt.Events?.Count ?? 0); - } - var commit = await _persistenceAsync.CommitAsync(attempt, cancellationToken).ConfigureAwait(false); - if (commit != null) - { - PopulateStream(StreamRevision + 1, attempt.StreamRevision, commit); - ClearChanges(); - } - return commit; - } - - private CommitAttempt BuildCommitAttempt(Guid commitId) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.BuildingCommitAttempt, commitId, StreamId, BucketId); - } - - return new CommitAttempt( - BucketId, - StreamId, - StreamRevision + _events.Count, - commitId, - CommitSequence + 1, - SystemTime.UtcNow, - CopyUncommittedHeaders(), - CopyUncommittedEvents()); - } - - private Dictionary CopyUncommittedHeaders() - { - var headers = new Dictionary(UncommittedHeaders.Count); - foreach (var header in UncommittedHeaders) - { - headers[header.Key] = header.Value; - } - - return headers; - } - - private EventMessage[] CopyUncommittedEvents() - { - var events = new EventMessage[_events.Count]; - _events.CopyTo(events, 0); - return events; - } - - /// - public void Dispose() - { - _disposed = true; - } - } -} diff --git a/src/NEventStore/OptimisticPipelineHook.cs b/src/NEventStore/OptimisticPipelineHook.cs deleted file mode 100644 index 4a843a4d5..000000000 --- a/src/NEventStore/OptimisticPipelineHook.cs +++ /dev/null @@ -1,308 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Logging; -using NEventStore.Persistence; -using System.Globalization; - -namespace NEventStore -{ - /// - /// Tracks the heads of streams to reduce latency by avoiding roundtrips to storage. - /// - public class OptimisticPipelineHook : PipelineHookBase - { - internal const int MaxStreamsToTrack = 100; - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(OptimisticPipelineHook)); - private readonly Dictionary _heads = []; //TODO use concurrent collections - private readonly LinkedList _maxItemsToTrack = new(); - private readonly int _maxStreamsToTrack; -#if NET9_0_OR_GREATER - private readonly Lock _lock = new(); -#else - private readonly Object _lock = new(); -#endif - - /// - /// Initializes a new instance of the OptimisticPipelineHook class. - /// - public OptimisticPipelineHook() - : this(MaxStreamsToTrack) - { } - - /// - /// Initializes a new instance of the OptimisticPipelineHook class. - /// - public OptimisticPipelineHook(int maxStreamsToTrack) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.TrackingStreams, maxStreamsToTrack); - } - _maxStreamsToTrack = maxStreamsToTrack; - } - - /// - public override ICommit? SelectCommit(ICommit committed) - { - Track(committed); - return committed; - } - - /// - public override bool PreCommit(CommitAttempt attempt) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.OptimisticConcurrencyCheck, attempt.StreamId); - } - - var head = GetStreamHead(GetHeadKey(attempt)); - if (head == null) - { - return true; - } - - if (head.CommitSequence >= attempt.CommitSequence) - { - throw new ConcurrencyException(String.Format( - CultureInfo.InvariantCulture, - Messages.ConcurrencyExceptionCommitSequence, - head.CommitSequence, - attempt.BucketId, - attempt.CommitSequence, - attempt.StreamId, - attempt.StreamRevision, - attempt.Events.Count - )); - } - - if (head.StreamRevision >= attempt.StreamRevision) - { - throw new ConcurrencyException(String.Format( - CultureInfo.InvariantCulture, - Messages.ConcurrencyExceptionStreamRevision, - head.StreamRevision, - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.Events.Count - )); - } - - if (head.CommitSequence < attempt.CommitSequence - 1) - { - throw new StorageException(String.Format( - CultureInfo.InvariantCulture, - Messages.StorageExceptionCommitSequence, - head.CommitSequence, - attempt.BucketId, - attempt.CommitSequence, - attempt.StreamId, - attempt.StreamRevision, - attempt.Events.Count - )); // beyond the end of the stream - } - - if (head.StreamRevision < attempt.StreamRevision - attempt.Events.Count) - { - throw new StorageException(String.Format( - CultureInfo.InvariantCulture, - Messages.StorageExceptionEndOfStream, - head.StreamRevision, - attempt.StreamRevision, - attempt.Events.Count, - attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision - )); // beyond the end of the stream - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.NoConflicts, attempt.StreamId, attempt.BucketId); - } - return true; - } - - /// - public override void PostCommit(ICommit committed) - { - Track(committed); - } - - /// - public override void OnPurge(string? bucketId) - { - lock (_lock) - { - if (bucketId == null) - { - _heads.Clear(); - _maxItemsToTrack.Clear(); - return; - } - HeadKey[] headsInBucket = _heads.Keys.Where(k => k.BucketId == bucketId).ToArray(); - foreach (var head in headsInBucket) - { - RemoveHead(head); - } - } - } - - /// - public override void OnDeleteStream(string bucketId, string streamId) - { - lock (_lock) - { - RemoveHead(new HeadKey(bucketId, streamId)); - } - } - - /// - protected override void Dispose(bool disposing) - { - _heads.Clear(); - _maxItemsToTrack.Clear(); - base.Dispose(disposing); - } - - /// - public void Track(ICommit committed) - { - if (committed == null) - { - return; - } - - lock (_lock) - { - UpdateStreamHead(committed); - TrackUpToCapacity(committed); - } - } - - private void UpdateStreamHead(ICommit committed) - { - HeadKey headKey = GetHeadKey(committed); - var head = GetStreamHead(headKey); - if (AlreadyTracked(head)) - { - _maxItemsToTrack.Remove(headKey); - } - - head ??= committed; - head = head.StreamRevision > committed.StreamRevision ? head : committed; - - _heads[headKey] = head; - } - - private void RemoveHead(HeadKey head) - { - _heads.Remove(head); - var node = _maxItemsToTrack.Find(head); // There should only be ever one or none - if (node != null) - { - _maxItemsToTrack.Remove(node); - } - } - - private static bool AlreadyTracked(ICommit? head) - { - return head != null; - } - - private void TrackUpToCapacity(ICommit committed) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.TrackingCommit, committed.CommitSequence, committed.StreamId, committed.BucketId); - } - _maxItemsToTrack.AddFirst(GetHeadKey(committed)); - if (_maxItemsToTrack.Count <= _maxStreamsToTrack) - { - return; - } - - HeadKey expired = _maxItemsToTrack.Last!.Value; - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Resources.NoLongerTrackingStream, expired.StreamId, expired.BucketId); - } - - _heads.Remove(expired); - _maxItemsToTrack.RemoveLast(); - } - - /// - public bool Contains(ICommit attempt) - { - return GetStreamHead(GetHeadKey(attempt)) != null; - } - - private ICommit? GetStreamHead(HeadKey headKey) - { - lock (_lock) - { - _heads.TryGetValue(headKey, out ICommit? head); - return head; - } - } - - private static HeadKey GetHeadKey(ICommit commit) - { - return new HeadKey(commit.BucketId, commit.StreamId); - } - - private static HeadKey GetHeadKey(CommitAttempt commitAttempt) - { - return new HeadKey(commitAttempt.BucketId, commitAttempt.StreamId); - } - - private sealed class HeadKey : IEquatable - { - public string BucketId { get; } - - public string StreamId { get; } - - public HeadKey(string bucketId, string streamId) - { - BucketId = bucketId; - StreamId = streamId; - } - - public bool Equals(HeadKey? other) - { - if (other is null) - { - return false; - } - if (ReferenceEquals(this, other)) - { - return true; - } - return String.Equals(BucketId, other.BucketId, StringComparison.Ordinal) - && String.Equals(StreamId, other.StreamId, StringComparison.Ordinal); - } - - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - return obj is HeadKey headKey && Equals(headKey); - } - - public override int GetHashCode() - { - unchecked - { - return (BucketId.GetHashCode() * 397) ^ StreamId.GetHashCode(); - } - } - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/Commit.cs b/src/NEventStore/Persistence/Commit.cs deleted file mode 100644 index ee7ab08e6..000000000 --- a/src/NEventStore/Persistence/Commit.cs +++ /dev/null @@ -1,67 +0,0 @@ -#pragma warning disable RCS1170 // Use read-only auto-implemented property. - -namespace NEventStore.Persistence -{ - /// - /// Represents a commit to the event store. - /// - public class Commit : ICommit - { - /// - /// Initializes a new instance of the Commit class. - /// - public Commit( - string bucketId, - string streamId, - int streamRevision, - Guid commitId, - int commitSequence, - DateTime commitStamp, - Int64 checkpointToken, - IDictionary? headers, - ICollection? events) - { - BucketId = bucketId; - StreamId = streamId; - StreamRevision = streamRevision; - CommitId = commitId; - CommitSequence = commitSequence; - CommitStamp = commitStamp; - CheckpointToken = checkpointToken; - Headers = headers ?? new Dictionary(); - Events = events ?? Array.Empty(); - //Events = events == null ? - // new ReadOnlyCollection(new List()) : - // new ReadOnlyCollection(new List(events)); - } - - /// - public string BucketId { get; private set; } - - /// - public string StreamId { get; private set; } - - /// - public int StreamRevision { get; private set; } - - /// - public Guid CommitId { get; private set; } - - /// - public int CommitSequence { get; private set; } - - /// - public DateTime CommitStamp { get; private set; } - - /// - public IDictionary Headers { get; private set; } - - /// - public ICollection Events { get; private set; } - - /// - public Int64 CheckpointToken { get; private set; } - } -} - -#pragma warning restore RCS1170 // Use read-only auto-implemented property. \ No newline at end of file diff --git a/src/NEventStore/Persistence/IPersistStreams.cs b/src/NEventStore/Persistence/IPersistStreams.cs deleted file mode 100644 index a8d3b93c6..000000000 --- a/src/NEventStore/Persistence/IPersistStreams.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NEventStore.Persistence -{ - /// - /// Indicates the ability to adapt the underlying persistence infrastructure to behave like a stream of events. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPersistStreams : IDisposable - , IPersistStreamsSync - , IPersistStreamsAsync - { - /// - /// Gets a value indicating whether this instance has been disposed of. - /// - bool IsDisposed { get; } - - /// - /// Initializes and prepares the storage for use, if not already performed. - /// - /// - /// Store initialization will be synchronous and should be completed before the method returns. - /// This is to ensure that the storage is ready for use before the built storage instance is registered in a container. - /// Another option will be: remove the Wireup.InitializeStorageEngine() method and let the user call Initialize() or InitializeAsync() explicitly. - /// - /// - /// - void Initialize(); - - /// - /// Completely DESTROYS the contents and schema (if applicable) containing ANY and ALL streams that have been - /// successfully persisted. - /// Use with caution. - /// - void Drop(); - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/IPersistStreamsAsync.cs b/src/NEventStore/Persistence/IPersistStreamsAsync.cs deleted file mode 100644 index 9cef3cceb..000000000 --- a/src/NEventStore/Persistence/IPersistStreamsAsync.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace NEventStore.Persistence -{ - /// - /// Asynchronous Interface: Indicates the ability to adapt the underlying persistence infrastructure to behave like a stream of events. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPersistStreamsAsync : ICommitEventsAsync, IAccessSnapshotsAsync - { - /// - /// Gets all commits (from all the buckets) after the specified checkpoint (excluded). Use 0 to get from the beginning. - /// - /// The checkpoint token: all the commits after this one will be returned. - /// The observer to receive the commits. - /// The token to monitor for cancellation requests. - /// An enumerable of Commits. - /// - /// - Task GetFromAsync(Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); - - /// - /// Gets all commits (from all the buckets) after the specified checkpoint token (excluded) up to the specified end checkpoint token (included). - /// - /// The checkpoint token: all the commits after this one will be returned - /// The checkpoint token: all the commits tp to this one (included) will be returned - /// The observer to receive the commits. - /// The token to monitor for cancellation requests. - /// All commits that have occurred on or after the specified checkpoint token up to the specified end checkpoint token. - /// - /// - Task GetFromToAsync(Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); - - /// - /// Gets all commits after the specified checkpoint (excluded) for a specific bucket. Use 0 to get from the beginning. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The checkpoint token: all the commits after this one will be returned - /// The observer to receive the commits. - /// The token to monitor for cancellation requests. - /// An enumerable of Commits. - /// - /// - Task GetFromAsync(string bucketId, Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); - - /// - /// Gets all commits after the specified checkpoint token (excluded) up to the specified end checkpoint token (included). - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The checkpoint token: all the commits after this one will be returned - /// The checkpoint token: all the commits tp to this one (included) will be returned - /// The observer to receive the commits. - /// The token to monitor for cancellation requests. - /// All commits that have occurred on or after the specified checkpoint token up to the specified end checkpoint token. - /// - /// - Task GetFromToAsync(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken = default); - - /// - /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted. - /// Use with caution. - /// - Task PurgeAsync(CancellationToken cancellationToken = default); - - /// - /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted - /// in the specified bucket. - /// Use with caution. - /// - Task PurgeAsync(string bucketId, CancellationToken cancellationToken = default); - - /// - /// Deletes a stream. - /// - /// The bucket Id from which the stream is to be deleted. - /// The stream Id of the stream that is to be deleted. - /// The token to monitor for cancellation requests. - Task DeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken = default); - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/IPersistStreamsSync.cs b/src/NEventStore/Persistence/IPersistStreamsSync.cs deleted file mode 100644 index b6f504ded..000000000 --- a/src/NEventStore/Persistence/IPersistStreamsSync.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace NEventStore.Persistence -{ - /// - /// Synchronous Interface: Indicates the ability to adapt the underlying persistence infrastructure to behave like a stream of events. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPersistStreamsSync : ICommitEvents, IAccessSnapshots - { - /// - /// Gets all commits on or after the specified starting time. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The point in time at which to start. - /// All commits that have occurred on or after the specified starting time. - /// - /// - [Obsolete("DateTime is problematic in distributed systems. Use GetFrom(Int64 checkpointToken) instead. This method will be removed in a later version.")] - IEnumerable GetFrom(string bucketId, DateTime startDate); - - /// - /// Gets all commits on or after the specified starting time and before the specified end time. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The point in time at which to start. - /// The point in time at which to end. - /// All commits that have occurred on or after the specified starting time and before the end time. - /// - /// - [Obsolete("DateTime is problematic in distributed systems. Use GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) instead. This method will be removed in a later version.")] - IEnumerable GetFromTo(string bucketId, DateTime startDate, DateTime endDate); - - /// - /// Gets all commits (from all the buckets) after the specified checkpoint (excluded). Use 0 to get from the beginning. - /// - /// The checkpoint token: all the commits after this one will be returned. - /// An enumerable of Commits. - /// - /// - IEnumerable GetFrom(Int64 checkpointToken); - - /// - /// Gets all commits (from all the buckets) after the specified checkpoint token (excluded) up to the specified end checkpoint token (included). - /// - /// The checkpoint token: all the commits after this one will be returned - /// The checkpoint token: all the commits tp to this one (included) will be returned - /// All commits that have occurred on or after the specified checkpoint token up to the specified end checkpoint token. - /// - /// - IEnumerable GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken); - - /// - /// Gets all commits after the specified checkpoint (excluded) for a specific bucket. Use 0 to get from the beginning. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The checkpoint token: all the commits after this one will be returned - /// An enumerable of Commits. - /// - /// - IEnumerable GetFrom(string bucketId, Int64 checkpointToken); - - /// - /// Gets all commits after the specified checkpoint token (excluded) up to the specified end checkpoint token (included). - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The checkpoint token: all the commits after this one will be returned - /// The checkpoint token: all the commits tp to this one (included) will be returned - /// All commits that have occurred on or after the specified checkpoint token up to the specified end checkpoint token. - /// - /// - IEnumerable GetFromTo(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken); - - /// - /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted. - /// Use with caution. - /// - void Purge(); - - /// - /// Completely DESTROYS the contents of ANY and ALL streams that have been successfully persisted - /// in the specified bucket. - /// Use with caution. - /// - void Purge(string bucketId); - - /// - /// Deletes a stream. - /// - /// The bucket Id from which the stream is to be deleted. - /// The stream Id of the stream that is to be deleted. - void DeleteStream(string bucketId, string streamId); - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/IPersistenceFactory.cs b/src/NEventStore/Persistence/IPersistenceFactory.cs deleted file mode 100644 index eb4eff4b1..000000000 --- a/src/NEventStore/Persistence/IPersistenceFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NEventStore.Persistence -{ - /// - /// Indicates the ability to build a ready-to-use persistence engine. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IPersistenceFactory - { - /// - /// Builds a persistence engine. - /// - /// A ready-to-use persistence engine. - IPersistStreams Build(); - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/IStreamHead.cs b/src/NEventStore/Persistence/IStreamHead.cs deleted file mode 100644 index c70a329ed..000000000 --- a/src/NEventStore/Persistence/IStreamHead.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace NEventStore.Persistence -{ - /// - /// Indicates the most recent information representing the head of a given stream. - /// - public interface IStreamHead - { - /// - /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. - /// - string BucketId { get; } - - /// - /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. - /// - string StreamId { get; } - - /// - /// Gets the value which indicates the revision, length, or number of events committed to the stream. - /// - int HeadRevision { get; } - - /// - /// Gets the value which indicates the revision at which the last snapshot was taken. - /// - int SnapshotRevision { get; } - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs b/src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs deleted file mode 100644 index f82850456..000000000 --- a/src/NEventStore/Persistence/InMemory/InMemoryPersistenceEngine.cs +++ /dev/null @@ -1,1176 +0,0 @@ -using System.Collections.Concurrent; -using System.Globalization; -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore.Persistence.InMemory -{ - /// - /// Represents an in-memory persistence engine. - /// - public class InMemoryPersistenceEngine : IPersistStreams - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(InMemoryPersistenceEngine)); - private readonly ConcurrentDictionary _buckets = new(); - // Keep a process-wide checkpoint-ordered index so global checkpoint reads do not need - // to flatten every bucket and sort the combined result on each query. - private readonly List _commitsByCheckpoint = []; -#if NET9_0_OR_GREATER - private readonly Lock _commitsByCheckpointSync = new(); -#else - private readonly object _commitsByCheckpointSync = new(); -#endif - private bool _disposed; - private long _checkpoint; - - private Bucket this[string bucketId] - { - get { return _buckets.GetOrAdd(bucketId, _ => new Bucket()); } - } - - /// - /// Disposes the engine. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Initializes the engine. - /// - public void Initialize() - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.InitializingEngine); - } - } - - /// - [Obsolete("DateTime is problematic in distributed systems. Use GetFrom(Int64 checkpointToken) instead. This method will be removed in a later version.")] - public IEnumerable GetFrom(string bucketId, DateTime startDate) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingAllCommitsFromTime, bucketId, startDate); - } - return this[bucketId].GetFrom(startDate); - } - - /// - [Obsolete("DateTime is problematic in distributed systems. Use GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) instead. This method will be removed in a later version.")] - public IEnumerable GetFromTo(string bucketId, DateTime startDate, DateTime endDate) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingAllCommitsFromToTime, startDate, endDate); - } - return this[bucketId].GetFromTo(startDate, endDate); - } - - /// - public IEnumerable GetFrom(Int64 checkpointToken) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingAllCommitsFromCheckpoint, checkpointToken); - } - - lock (_commitsByCheckpointSync) - { - return GetCheckpointRange(_commitsByCheckpoint, checkpointToken, long.MaxValue); - } - } - - /// - public Task GetFromAsync(Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return ObserveDataStream(() => GetFrom(checkpointToken), asyncObserver, cancellationToken); - } - - /// - public IEnumerable GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingCommitsFromToCheckpoint, fromCheckpointToken, toCheckpointToken); - } - - lock (_commitsByCheckpointSync) - { - return GetCheckpointRange(_commitsByCheckpoint, fromCheckpointToken, toCheckpointToken); - } - } - - /// - public Task GetFromToAsync(Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return ObserveDataStream(() => GetFromTo(fromCheckpointToken, toCheckpointToken), asyncObserver, cancellationToken); - } - - /// - public IEnumerable GetFrom(string bucketId, Int64 checkpointToken) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingAllCommitsFromBucketAndCheckpoint, bucketId, checkpointToken); - } - return this[bucketId].GetFrom(checkpointToken); - } - - /// - public Task GetFromAsync(string bucketId, Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return ObserveDataStream(() => GetFrom(bucketId, checkpointToken), asyncObserver, cancellationToken); - } - - /// - public IEnumerable GetFromTo(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingCommitsFromBucketAndFromToCheckpoint, bucketId, fromCheckpointToken, toCheckpointToken); - } - return this[bucketId].GetFromTo(fromCheckpointToken, toCheckpointToken); - } - - /// - public Task GetFromToAsync(string bucketId, long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return ObserveDataStream(() => GetFromTo(bucketId, fromCheckpointToken, toCheckpointToken), asyncObserver, cancellationToken); - } - - /// - public IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingAllCommitsFromRevision, streamId, bucketId, minRevision, maxRevision); - } - return this[bucketId].GetFrom(streamId, minRevision, maxRevision); - } - - private long NextCheckpoint() - { - return Interlocked.Increment(ref _checkpoint); - } - - /// - public ICommit? Commit(CommitAttempt attempt) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.AttemptingToCommit, attempt.CommitId, attempt.StreamId, attempt.BucketId, attempt.CommitSequence); - } - var commit = this[attempt.BucketId].Commit(attempt, NextCheckpoint); - RegisterCommit(commit); - return commit; - } - - /// - public Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken) - { - return ObserveDataStream(() => GetFrom(bucketId, streamId, minRevision, maxRevision), observer, cancellationToken); - } - - private static async Task ObserveDataStream(Func> dataProvider, IAsyncObserver observer, CancellationToken cancellationToken) - { - try - { - var data = dataProvider(); - if (data?.Any() == true) - { - foreach (var commit in data) - { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException("Operation Cancellation Requested"); - } - var goOn = await observer.OnNextAsync(commit, cancellationToken).ConfigureAwait(false); - if (!goOn) - { - break; - } - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException("Operation Cancellation Requested"); - } - } - } - await observer.OnCompletedAsync(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) - { - // Normal cooperative cancellation. - } - catch (Exception ex) - { - await observer.OnErrorAsync(ex, cancellationToken).ConfigureAwait(false); - } - } - - /// - public Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) - { - return Task.FromResult(Commit(attempt)); - } - - /// - public ISnapshot? GetSnapshot(string bucketId, string streamId, int maxRevision) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingSnapshotForStream, bucketId, streamId, maxRevision); - } - return this[bucketId].GetSnapshot(streamId, maxRevision); - } - - /// - public bool AddSnapshot(ISnapshot snapshot) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.AddingSnapshot, snapshot.BucketId, snapshot.StreamId, snapshot.StreamRevision); - } - return this[snapshot.BucketId].AddSnapshot(snapshot); - } - - /// - public IEnumerable GetStreamsToSnapshot(string bucketId, int maxThreshold) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingStreamsToSnapshot, bucketId, maxThreshold); - } - return this[bucketId].GetStreamsToSnapshot(maxThreshold); - } - - /// - public Task GetSnapshotAsync(string bucketId, string streamId, int maxRevision, CancellationToken cancellationToken) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.GettingSnapshotForStream, bucketId, streamId, maxRevision); - } - return Task.FromResult(this[bucketId].GetSnapshot(streamId, maxRevision)); - } - - /// - public Task AddSnapshotAsync(ISnapshot snapshot, CancellationToken cancellationToken) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.AddingSnapshot, snapshot.BucketId, snapshot.StreamId, snapshot.StreamRevision); - } - return Task.FromResult(this[snapshot.BucketId].AddSnapshot(snapshot)); - } - - /// - public async Task GetStreamsToSnapshotAsync(string bucketId, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - try - { - var data = GetStreamsToSnapshot(bucketId, maxThreshold); - if (data?.Any() == true) - { - foreach (var commit in data) - { - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException("Operation Cancellation Requested"); - } - var goOn = await asyncObserver.OnNextAsync(commit, cancellationToken).ConfigureAwait(false); - if (!goOn) - { - break; - } - if (cancellationToken.IsCancellationRequested) - { - throw new OperationCanceledException("Operation Cancellation Requested"); - } - } - } - await asyncObserver.OnCompletedAsync(cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - await asyncObserver.OnErrorAsync(ex, cancellationToken).ConfigureAwait(false); - } - } - - /// - public void Purge() - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Warning)) - { - Logger.LogWarning(Resources.PurgingStore); - } - - foreach (var bucket in _buckets.Values) - { - bucket.Purge(); - } - - lock (_commitsByCheckpointSync) - { - _commitsByCheckpoint.Clear(); - } - - Interlocked.Exchange(ref _checkpoint, 0); - } - - /// - public void Purge(string bucketId) - { - ThrowWhenDisposed(); - if (_buckets.TryRemove(bucketId, out Bucket? bucket)) - { - RemoveGlobalCommits(bucket.Purge()); - } - } - - /// - public Task PurgeAsync(CancellationToken cancellationToken) - { - Purge(); - return Task.CompletedTask; - } - - /// - public Task PurgeAsync(string bucketId, CancellationToken cancellationToken) - { - Purge(bucketId); - return Task.CompletedTask; - } - - /// - public void Drop() - { - ThrowWhenDisposed(); - - _buckets.Clear(); - - lock (_commitsByCheckpointSync) - { - _commitsByCheckpoint.Clear(); - } - - Interlocked.Exchange(ref _checkpoint, 0); - } - - /// - public void DeleteStream(string bucketId, string streamId) - { - ThrowWhenDisposed(); - if (Logger.IsEnabled(LogLevel.Warning)) - { - Logger.LogWarning(Resources.DeletingStream, streamId, bucketId); - } - if (!_buckets.TryGetValue(bucketId, out Bucket? bucket)) - { - return; - } - RemoveGlobalCommits(bucket.DeleteStream(streamId)); - } - - /// - public Task DeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken) - { - DeleteStream(bucketId, streamId); - return Task.CompletedTask; - } - - /// - public bool IsDisposed - { - get { return _disposed; } - } - -#pragma warning disable RCS1163 // Unused parameter. -#pragma warning disable IDE0060 // Remove unused parameter - private void Dispose(bool disposing) -#pragma warning restore IDE0060 // Remove unused parameter -#pragma warning restore RCS1163 // Unused parameter. - { - _disposed = true; - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.DisposingEngine); - } - } - - private void ThrowWhenDisposed() - { - if (!_disposed) - { - return; - } - if (Logger.IsEnabled(LogLevel.Warning)) - { - Logger.LogWarning(Resources.AlreadyDisposed); - } - throw new ObjectDisposedException(Resources.AlreadyDisposed); - } - - private void RegisterCommit(InMemoryCommit commit) - { - lock (_commitsByCheckpointSync) - { - // Checkpoints are monotonically increasing, but use a binary-search insert point - // so the index stays correct even if the storage strategy changes later. - int insertIndex = FindCheckpointInsertIndex(_commitsByCheckpoint, commit.CheckpointToken); - _commitsByCheckpoint.Insert(insertIndex, commit); - } - } - - private void RemoveGlobalCommits(IReadOnlyCollection commits) - { - if (commits.Count == 0) - { - return; - } - - lock (_commitsByCheckpointSync) - { - // Delete and purge operations must update the global checkpoint index as well, - // otherwise later reads would return stale commits that no longer exist in a bucket. - foreach (var commit in commits) - { - int index = FindCommitIndex(_commitsByCheckpoint, commit.CheckpointToken); - if (index >= 0) - { - _commitsByCheckpoint.RemoveAt(index); - } - } - } - } - - private static ICommit[] GetCheckpointRange(List commits, long fromCheckpointExclusive, long toCheckpointInclusive) - { - // Reads are defined as (from, to], so find the first commit strictly after the lower - // bound and the first commit strictly after the upper bound, then copy the slice. - int startIndex = FindFirstCheckpointAfter(commits, fromCheckpointExclusive); - if (startIndex >= commits.Count) - { - return []; - } - - int endIndex = FindFirstCheckpointAfter(commits, toCheckpointInclusive); - int count = endIndex - startIndex; - if (count <= 0) - { - return []; - } - - var results = new ICommit[count]; - for (int i = 0; i < count; i++) - { - results[i] = commits[startIndex + i]; - } - - return results; - } - - private static int FindFirstCheckpointAfter(List commits, long checkpoint) - { - int low = 0; - int high = commits.Count; - - while (low < high) - { - int mid = low + ((high - low) / 2); - if (commits[mid].CheckpointToken <= checkpoint) - { - low = mid + 1; - } - else - { - high = mid; - } - } - - return low; - } - - private static int FindCheckpointInsertIndex(List commits, long checkpoint) - { - int low = 0; - int high = commits.Count; - - while (low < high) - { - int mid = low + ((high - low) / 2); - if (commits[mid].CheckpointToken < checkpoint) - { - low = mid + 1; - } - else - { - high = mid; - } - } - - return low; - } - - private static int FindCommitIndex(List commits, long checkpoint) - { - int low = 0; - int high = commits.Count - 1; - - while (low <= high) - { - int mid = low + ((high - low) / 2); - long currentCheckpoint = commits[mid].CheckpointToken; - if (currentCheckpoint == checkpoint) - { - return mid; - } - - if (currentCheckpoint < checkpoint) - { - low = mid + 1; - } - else - { - high = mid - 1; - } - } - - return -1; - } - - private class InMemoryCommit : Commit - { - public InMemoryCommit( - string bucketId, - string streamId, - int streamRevision, - Guid commitId, - int commitSequence, - DateTime commitStamp, - Int64 checkpointToken, - IDictionary headers, - ICollection events) - : base(bucketId, streamId, streamRevision, commitId, commitSequence, commitStamp, checkpointToken, headers, events) - { } - } - - private class IdentityForConcurrencyConflictDetection - { - protected bool Equals(IdentityForConcurrencyConflictDetection other) - { - return string.Equals(this.streamId, other.streamId, StringComparison.Ordinal) - && string.Equals(this.bucketId, other.bucketId, StringComparison.Ordinal) - && this.commitSequence == other.commitSequence; - } - - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - if (obj.GetType() != this.GetType()) - { - return false; - } - return Equals((IdentityForConcurrencyConflictDetection)obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = this.streamId.GetHashCode(); - hashCode = (hashCode * 397) ^ this.bucketId.GetHashCode(); - return (hashCode * 397) ^ this.commitSequence; - } - } - - private readonly int commitSequence; - - private readonly string bucketId; - - private readonly string streamId; - - public IdentityForConcurrencyConflictDetection(CommitAttempt commitAttempt) - { - bucketId = commitAttempt.BucketId; - streamId = commitAttempt.StreamId; - commitSequence = commitAttempt.CommitSequence; - } - - public IdentityForConcurrencyConflictDetection(Commit commit) - { - bucketId = commit.BucketId; - streamId = commit.StreamId; - commitSequence = commit.CommitSequence; - } - } - - private class IdentityForDuplicationDetection - { - protected bool Equals(IdentityForDuplicationDetection other) - { - return string.Equals(this.streamId, other.streamId, StringComparison.Ordinal) - && string.Equals(this.bucketId, other.bucketId, StringComparison.Ordinal) - && this.commitId.Equals(other.commitId); - } - - public override bool Equals(object? obj) - { - if (obj is null) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - if (obj.GetType() != this.GetType()) - { - return false; - } - return Equals((IdentityForDuplicationDetection)obj); - } - - public override int GetHashCode() - { - unchecked - { - int hashCode = this.streamId.GetHashCode(); - hashCode = (hashCode * 397) ^ this.bucketId.GetHashCode(); - return (hashCode * 397) ^ this.commitId.GetHashCode(); - } - } - - private readonly Guid commitId; - - private readonly string bucketId; - - private readonly string streamId; - - public IdentityForDuplicationDetection(CommitAttempt commitAttempt) - { - bucketId = commitAttempt.BucketId; - streamId = commitAttempt.StreamId; - commitId = commitAttempt.CommitId; - } - - public IdentityForDuplicationDetection(Commit commit) - { - bucketId = commit.BucketId; - streamId = commit.StreamId; - commitId = commit.CommitId; - } - } - - private class Bucket - { -#if NET9_0_OR_GREATER - private readonly Lock _sync = new(); -#else - private readonly object _sync = new(); -#endif - // Maintain bucket-local commits in checkpoint order for checkpoint paging APIs. - private readonly List _commitsByCheckpoint = []; - // Maintain per-stream commits ordered by StreamRevision so stream reads can jump - // directly to the relevant commit range instead of scanning the entire bucket. - private readonly Dictionary> _commitsByStreamId = []; - private readonly HashSet _potentialDuplicates = []; - private readonly HashSet _potentialConflicts = []; - - // Stream-head reads and updates are keyed by StreamId, so keep a direct dictionary - // instead of repeatedly scanning a linked list. We still replace the entire StreamHead - // object on each update so callers always observe an immutable snapshot of the stream's - // current metadata instead of a mutable shared instance. - private readonly Dictionary _headsByStreamId = []; - // Snapshots are also queried by StreamId first and then by the greatest revision that is - // <= maxRevision. Grouping them per stream lets us avoid scanning snapshots that belong - // to unrelated streams, while keeping each stream's list sorted by StreamRevision so the - // "latest prior snapshot" lookup remains a binary-search operation. - private readonly Dictionary> _snapshotsByStreamId = []; - private readonly Dictionary _stamps = []; - - public IEnumerable GetFrom(string streamId, int minRevision, int maxRevision) - { - lock (_sync) - { - if (!_commitsByStreamId.TryGetValue(streamId, out List? commits)) - { - return []; - } - - int startIndex = FindFirstCommitEndingAtOrAfterRevision(commits, minRevision); - if (startIndex >= commits.Count) - { - return []; - } - - // Commits remain ordered by StreamRevision, so once a commit starts beyond the - // requested max revision the rest of the stream will also be out of range. - var results = new List(commits.Count - startIndex); - for (int i = startIndex; i < commits.Count; i++) - { - InMemoryCommit commit = commits[i]; - if ((commit.StreamRevision - commit.Events.Count + 1) > maxRevision) - { - break; - } - - results.Add(commit); - } - - return results.ToArray(); - } - } - - public IEnumerable GetFrom(DateTime start) - { - Guid commitId = _stamps.Where(x => x.Value >= start).Select(x => x.Key).FirstOrDefault(); - if (commitId == Guid.Empty) - { - return []; - } - - lock (_sync) - { - InMemoryCommit? startingCommit = _commitsByCheckpoint.FirstOrDefault(x => x.CommitId == commitId); - if (startingCommit == null) - { - return []; - } - - int startIndex = FindCheckpointInsertIndex(_commitsByCheckpoint, startingCommit.CheckpointToken); - return CopyCheckpointRange(_commitsByCheckpoint, startIndex, _commitsByCheckpoint.Count); - } - } - - public IEnumerable GetFrom(Int64 checkpoint) - { - lock (_sync) - { - int startIndex = FindFirstCheckpointAfter(_commitsByCheckpoint, checkpoint); - return CopyCheckpointRange(_commitsByCheckpoint, startIndex, _commitsByCheckpoint.Count); - } - } - - public IEnumerable GetFromTo(Int64 from, Int64 to) - { - lock (_sync) - { - int startIndex = FindFirstCheckpointAfter(_commitsByCheckpoint, from); - int endIndex = FindFirstCheckpointAfter(_commitsByCheckpoint, to); - return CopyCheckpointRange(_commitsByCheckpoint, startIndex, endIndex); - } - } - - public IEnumerable GetFromTo(DateTime start, DateTime end) - { - IEnumerable selectedCommitIds = _stamps.Where(x => x.Value >= start && x.Value < end).Select(x => x.Key).ToArray(); - Guid firstCommitId = selectedCommitIds.FirstOrDefault(); - Guid lastCommitId = selectedCommitIds.LastOrDefault(); - if (firstCommitId == Guid.Empty && lastCommitId == Guid.Empty) - { - return []; - } - lock (_sync) - { - InMemoryCommit? startingCommit = _commitsByCheckpoint.FirstOrDefault(x => x.CommitId == firstCommitId); - InMemoryCommit? endingCommit = _commitsByCheckpoint.FirstOrDefault(x => x.CommitId == lastCommitId); - int startingCommitIndex = startingCommit == null ? 0 : FindCheckpointInsertIndex(_commitsByCheckpoint, startingCommit.CheckpointToken); - int endingCommitIndex = endingCommit == null ? _commitsByCheckpoint.Count - 1 : FindCheckpointInsertIndex(_commitsByCheckpoint, endingCommit.CheckpointToken); - return CopyCheckpointRange(_commitsByCheckpoint, startingCommitIndex, endingCommitIndex + 1); - } - } - - public InMemoryCommit Commit(CommitAttempt attempt, Func nextCheckpoint) - { - lock (_sync) - { - DetectDuplicate(attempt); - if (_potentialConflicts.Contains(new IdentityForConcurrencyConflictDetection(attempt))) - { - throw new ConcurrencyException(); - } - long checkpoint = nextCheckpoint(); - var commit = new InMemoryCommit(attempt.BucketId, - attempt.StreamId, - attempt.StreamRevision, - attempt.CommitId, - attempt.CommitSequence, - attempt.CommitStamp, - checkpoint, - attempt.Headers, - attempt.Events); - - _stamps[commit.CommitId] = commit.CommitStamp; - InsertCommit(commit); - _potentialDuplicates.Add(new IdentityForDuplicationDetection(commit)); - _potentialConflicts.Add(new IdentityForConcurrencyConflictDetection(commit)); - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Resources.UpdatingStreamHead, commit.StreamId, commit.BucketId); - } - - // A commit advances the head revision but must preserve whatever snapshot - // revision was already recorded for the stream. Resetting that value would make - // GetStreamsToSnapshot believe the stream has never been snapshotted. - int snapshotRevision = _headsByStreamId.TryGetValue(commit.StreamId, out StreamHead? head) - ? head.SnapshotRevision - : 0; - UpdateStreamHead(commit.BucketId, commit.StreamId, commit.StreamRevision, snapshotRevision); - return commit; - } - } - - private void DetectDuplicate(CommitAttempt attempt) - { - if (_potentialDuplicates.Contains(new IdentityForDuplicationDetection(attempt))) - { - throw new DuplicateCommitException(String.Format( - CultureInfo.InvariantCulture, - Messages.DuplicateCommitIdException, attempt.StreamId, attempt.BucketId, attempt.CommitId)); - } - } - - public IEnumerable GetStreamsToSnapshot(int maxThreshold) - { - lock (_sync) - { - // Materialize the result while holding the lock. Returning a deferred iterator - // over the internal dictionary would expose callers to collection mutation after - // the lock is released and would break the multi-thread-safe IPersistStreams - // contract. - var streamsToSnapshot = new List(_headsByStreamId.Count); - foreach (StreamHead stream in _headsByStreamId.Values) - { - if (stream.HeadRevision >= stream.SnapshotRevision + maxThreshold) - { - streamsToSnapshot.Add(new StreamHead(stream.BucketId, stream.StreamId, stream.HeadRevision, stream.SnapshotRevision)); - } - } - - return streamsToSnapshot.ToArray(); - } - } - - public ISnapshot? GetSnapshot(string streamId, int maxRevision) - { - lock (_sync) - { - if (!_snapshotsByStreamId.TryGetValue(streamId, out List? snapshots)) - { - return null; - } - - int snapshotIndex = FindLastSnapshotAtOrBeforeRevision(snapshots, maxRevision); - return snapshotIndex < 0 ? null : snapshots[snapshotIndex]; - } - } - - public bool AddSnapshot(ISnapshot snapshot) - { - lock (_sync) - { - if (!_headsByStreamId.TryGetValue(snapshot.StreamId, out StreamHead? currentHead)) - { - return false; - } - - if (!_snapshotsByStreamId.TryGetValue(snapshot.StreamId, out List? snapshots)) - { - snapshots = []; - _snapshotsByStreamId.Add(snapshot.StreamId, snapshots); - } - - // if the snapshot is already there do NOT add it (follow the SQL implementation) - // and the original GetSnapshot behavior which was to return the first one that was - // added to the collection - int snapshotIndex = FindSnapshotInsertIndex(snapshots, snapshot.StreamRevision); - if (snapshotIndex < snapshots.Count && snapshots[snapshotIndex].StreamRevision == snapshot.StreamRevision) - { - return false; - } - - snapshots.Insert(snapshotIndex, snapshot); - - // Preserve the historic behavior where adding any snapshot, even an older one, - // updates the stream head's SnapshotRevision to that exact revision. - UpdateStreamHead(currentHead.BucketId, currentHead.StreamId, currentHead.HeadRevision, snapshot.StreamRevision); - } - return true; - } - - public IReadOnlyCollection Purge() - { - lock (_sync) - { - var removedCommits = _commitsByCheckpoint.ToArray(); - _commitsByCheckpoint.Clear(); - _commitsByStreamId.Clear(); - // Purge is the hard reset path for the bucket, so every secondary index must be - // cleared together with the primary commit indexes. Leaving any of them behind - // would surface stale stream heads or snapshots after the bucket is supposedly empty. - _snapshotsByStreamId.Clear(); - _headsByStreamId.Clear(); - _stamps.Clear(); - _potentialConflicts.Clear(); - _potentialDuplicates.Clear(); - return removedCommits; - } - } - - public IReadOnlyCollection DeleteStream(string streamId) - { - lock (_sync) - { - if (!_commitsByStreamId.TryGetValue(streamId, out List? commitsForStream)) - { - return []; - } - - var commits = commitsForStream.ToArray(); - foreach (var commit in commits) - { - int checkpointIndex = FindCommitIndex(_commitsByCheckpoint, commit.CheckpointToken); - if (checkpointIndex >= 0) - { - _commitsByCheckpoint.RemoveAt(checkpointIndex); - } - - _stamps.Remove(commit.CommitId); - } - - _commitsByStreamId.Remove(streamId); - // Stream deletion must remove every metadata shortcut for that stream in the - // same critical section as the commit removal. Otherwise later lookups could - // still find a head or snapshot that no longer has backing commits. - _snapshotsByStreamId.Remove(streamId); - _headsByStreamId.Remove(streamId); - - return commits; - } - } - - private void UpdateStreamHead(string bucketId, string streamId, int headRevision, int snapshotRevision) - { - // Store a fresh value object for each update so callers never get a reference that - // can be mutated behind their back, and so head reads always reflect a coherent pair - // of HeadRevision and SnapshotRevision values from the same write. - _headsByStreamId[streamId] = new StreamHead(bucketId, streamId, headRevision, snapshotRevision); - } - - private void InsertCommit(InMemoryCommit commit) - { - // Update both indexes under the same bucket lock so checkpoint reads and - // stream-revision reads always observe the same commit set. - int checkpointIndex = FindCheckpointInsertIndex(_commitsByCheckpoint, commit.CheckpointToken); - _commitsByCheckpoint.Insert(checkpointIndex, commit); - - if (!_commitsByStreamId.TryGetValue(commit.StreamId, out List? streamCommits)) - { - streamCommits = []; - _commitsByStreamId.Add(commit.StreamId, streamCommits); - } - - int streamIndex = FindStreamRevisionInsertIndex(streamCommits, commit.StreamRevision); - streamCommits.Insert(streamIndex, commit); - } - - private static ICommit[] CopyCheckpointRange(List commits, int startIndex, int endIndexExclusive) - { - int count = endIndexExclusive - startIndex; - if (count <= 0) - { - return []; - } - - var results = new ICommit[count]; - for (int i = 0; i < count; i++) - { - results[i] = commits[startIndex + i]; - } - - return results; - } - - private static int FindFirstCommitEndingAtOrAfterRevision(List commits, int revision) - { - // The stream index is sorted by each commit's ending revision, which is enough for - // the existing read contract because a commit belongs in the result if its ending - // revision is >= minRevision and its starting revision is <= maxRevision. - int low = 0; - int high = commits.Count; - - while (low < high) - { - int mid = low + ((high - low) / 2); - if (commits[mid].StreamRevision < revision) - { - low = mid + 1; - } - else - { - high = mid; - } - } - - return low; - } - - private static int FindStreamRevisionInsertIndex(List commits, int revision) - { - int low = 0; - int high = commits.Count; - - while (low < high) - { - int mid = low + ((high - low) / 2); - if (commits[mid].StreamRevision < revision) - { - low = mid + 1; - } - else - { - high = mid; - } - } - - return low; - } - - private static int FindSnapshotInsertIndex(List snapshots, int revision) - { - int low = 0; - int high = snapshots.Count; - - while (low < high) - { - int mid = low + ((high - low) / 2); - if (snapshots[mid].StreamRevision < revision) - { - low = mid + 1; - } - else - { - high = mid; - } - } - - return low; - } - - private static int FindLastSnapshotAtOrBeforeRevision(List snapshots, int revision) - { - int low = 0; - int high = snapshots.Count; - - while (low < high) - { - int mid = low + ((high - low) / 2); - if (snapshots[mid].StreamRevision <= revision) - { - low = mid + 1; - } - else - { - high = mid; - } - } - - return low - 1; - } - - private static int FindFirstCheckpointAfter(List commits, long checkpoint) - { - int low = 0; - int high = commits.Count; - - while (low < high) - { - int mid = low + ((high - low) / 2); - if (commits[mid].CheckpointToken <= checkpoint) - { - low = mid + 1; - } - else - { - high = mid; - } - } - - return low; - } - - private static int FindCheckpointInsertIndex(List commits, long checkpoint) - { - int low = 0; - int high = commits.Count; - - while (low < high) - { - int mid = low + ((high - low) / 2); - if (commits[mid].CheckpointToken < checkpoint) - { - low = mid + 1; - } - else - { - high = mid; - } - } - - return low; - } - - private static int FindCommitIndex(List commits, long checkpoint) - { - int low = 0; - int high = commits.Count - 1; - - while (low <= high) - { - int mid = low + ((high - low) / 2); - long currentCheckpoint = commits[mid].CheckpointToken; - if (currentCheckpoint == checkpoint) - { - return mid; - } - - if (currentCheckpoint < checkpoint) - { - low = mid + 1; - } - else - { - high = mid - 1; - } - } - - return -1; - } - } - } -} diff --git a/src/NEventStore/Persistence/InMemory/InMemoryPersistenceFactory.cs b/src/NEventStore/Persistence/InMemory/InMemoryPersistenceFactory.cs deleted file mode 100644 index 54e1d3d53..000000000 --- a/src/NEventStore/Persistence/InMemory/InMemoryPersistenceFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace NEventStore.Persistence.InMemory -{ - /// - /// Represents a factory for creating in-memory persistence engines. - /// - public class InMemoryPersistenceFactory : IPersistenceFactory - { - /// - /// Builds a new in-memory persistence engine. - /// - public virtual IPersistStreams Build() - { - return new InMemoryPersistenceEngine(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/PersistStreamsExtensions.cs b/src/NEventStore/Persistence/PersistStreamsExtensions.cs deleted file mode 100644 index 2d8c83620..000000000 --- a/src/NEventStore/Persistence/PersistStreamsExtensions.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace NEventStore.Persistence -{ - /// - /// Provides a set of extension methods for the interface. - /// - public static class PersistStreamsExtensions - { - /// - /// Deletes a stream from the default bucket. - /// - /// The IPersistStreams instance. - /// The stream id to be deleted. - public static void DeleteStream(this IPersistStreams persistStreams, string streamId) - { - persistStreams.DeleteStream(Bucket.Default, streamId); - } - - /// - /// Deletes a stream from the default bucket. - /// - /// The IPersistStreams instance. - /// The stream id to be deleted. - /// The token to monitor for cancellation requests. - public static Task DeleteStreamAsync(this IPersistStreams persistStreams, string streamId, CancellationToken cancellationToken = default) - { - return persistStreams.DeleteStreamAsync(Bucket.Default, streamId, cancellationToken); - } - - /// - /// Returns a single commit from any bucket. - /// Wrapper for the function in order to - /// return a single commit. - /// - /// The IPersistStreams instance. - /// The checkpoint token that mark the commit to read. - /// A single commit. - public static ICommit? GetCommit(this IPersistStreams persistStreams, Int64 checkpointToken) - { - return persistStreams.GetFromTo(checkpointToken - 1, checkpointToken).SingleOrDefault(); - } - - /// - /// Returns a single commit from any bucket. - /// Wrapper for the function in order to - /// return a single commit. - /// - /// The IPersistStreams instance. - /// The checkpoint token that mark the commit to read. - /// The token to monitor for cancellation requests. - /// A single commit. - public static async Task GetCommitAsync(this IPersistStreams persistStreams, Int64 checkpointToken, CancellationToken cancellationToken = default) - { - var observer = new CommitStreamObserver(); - await persistStreams.GetFromToAsync(checkpointToken - 1, checkpointToken, observer, cancellationToken).ConfigureAwait(false); - return observer.Commits.SingleOrDefault(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/PipelineHooksAwarePersistStreamsDecorator.cs b/src/NEventStore/Persistence/PipelineHooksAwarePersistStreamsDecorator.cs deleted file mode 100644 index 113f1491c..000000000 --- a/src/NEventStore/Persistence/PipelineHooksAwarePersistStreamsDecorator.cs +++ /dev/null @@ -1,390 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore.Persistence -{ - /// - /// Represents a persistence decorator that allows for hooks to be injected into the pipeline. - /// - public sealed class PipelineHooksAwarePersistStreamsDecorator : IPersistStreams - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(PipelineHooksAwarePersistStreamsDecorator)); - private readonly IPersistStreams _original; - private readonly IEnumerable _pipelineHooks; - private readonly IEnumerable _pipelineHooksAsync; - - /// - /// Initializes a new instance of the PipelineHooksAwarePersistStreamsDecorator class. - /// - /// - public PipelineHooksAwarePersistStreamsDecorator(IPersistStreams original, IEnumerable pipelineHooks, IEnumerable pipelineHooksAsync) - { - _original = original ?? throw new ArgumentNullException(nameof(original)); - _pipelineHooks = pipelineHooks ?? throw new ArgumentNullException(nameof(pipelineHooks)); - _pipelineHooksAsync = pipelineHooksAsync ?? throw new ArgumentNullException(nameof(pipelineHooksAsync)); - } - - /// - public void Dispose() - { - _original.Dispose(); - } - - /// - [Obsolete("DateTime is problematic in distributed systems. Use GetFrom(Int64 checkpointToken) instead. This method will be removed in a later version.")] - public IEnumerable GetFrom(string bucketId, DateTime startDate) - { - return ExecuteSelectCommitsHooks(_original.GetFrom(bucketId, startDate)); - } - - /// - [Obsolete("DateTime is problematic in distributed systems. Use GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) instead. This method will be removed in a later version.")] - public IEnumerable GetFromTo(string bucketId, DateTime startDate, DateTime endDate) - { - return ExecuteSelectCommitsHooks(_original.GetFromTo(bucketId, startDate, endDate)); - } - - /// - public IEnumerable GetFrom(Int64 checkpointToken) - { - return ExecuteSelectCommitsHooks(_original.GetFrom(checkpointToken)); - } - - /// - public Task GetFromAsync(Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, asyncObserver); - return _original.GetFromAsync(checkpointToken, pipelineHookObserver, cancellationToken); - } - - /// - public IEnumerable GetFromTo(Int64 fromCheckpointToken, Int64 toCheckpointToken) - { - return ExecuteSelectCommitsHooks(_original.GetFromTo(fromCheckpointToken, toCheckpointToken)); - } - - /// - public Task GetFromToAsync(Int64 fromCheckpointToken, Int64 toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, asyncObserver); - return _original.GetFromToAsync(fromCheckpointToken, toCheckpointToken, pipelineHookObserver, cancellationToken); - } - - /// - public IEnumerable GetFrom(string bucketId, Int64 checkpointToken) - { - return ExecuteSelectCommitsHooks(_original.GetFrom(bucketId, checkpointToken)); - } - - /// - public Task GetFromAsync(string bucketId, Int64 checkpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, asyncObserver); - return _original.GetFromAsync(bucketId, checkpointToken, pipelineHookObserver, cancellationToken); - } - - /// - public IEnumerable GetFromTo(string bucketId, Int64 fromCheckpointToken, Int64 toCheckpointToken) - { - return ExecuteSelectCommitsHooks(_original.GetFromTo(bucketId, fromCheckpointToken, toCheckpointToken)); - } - - /// - public Task GetFromToAsync(string bucketId, long fromCheckpointToken, long toCheckpointToken, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, asyncObserver); - return _original.GetFromToAsync(bucketId, fromCheckpointToken, toCheckpointToken, pipelineHookObserver, cancellationToken); - } - - /// - public IEnumerable GetFrom(string bucketId, string streamId, int minRevision, int maxRevision) - { - return ExecuteSelectCommitsHooks(_original.GetFrom(bucketId, streamId, minRevision, maxRevision)); - } - - /// - public ICommit? Commit(CommitAttempt attempt) - { - return _original.Commit(attempt); - } - - /// - public Task GetFromAsync(string bucketId, string streamId, int minRevision, int maxRevision, IAsyncObserver observer, CancellationToken cancellationToken) - { - var pipelineHookObserver = new PipelineHookObserver(_pipelineHooks, _pipelineHooksAsync, observer); - return _original.GetFromAsync(bucketId, streamId, minRevision, maxRevision, pipelineHookObserver, cancellationToken); - } - - /// - public Task CommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) - { - return _original.CommitAsync(attempt, cancellationToken); - } - - /// - public ISnapshot? GetSnapshot(string bucketId, string streamId, int maxRevision) - { - return _original.GetSnapshot(bucketId, streamId, maxRevision); - } - - /// - public bool AddSnapshot(ISnapshot snapshot) - { - return _original.AddSnapshot(snapshot); - } - - /// - public IEnumerable GetStreamsToSnapshot(string bucketId, int maxThreshold) - { - return _original.GetStreamsToSnapshot(bucketId, maxThreshold); - } - - /// - public Task GetSnapshotAsync(string bucketId, string streamId, int maxRevision, CancellationToken cancellationToken) - { - return _original.GetSnapshotAsync(bucketId, streamId, maxRevision, cancellationToken); - } - - /// - public Task AddSnapshotAsync(ISnapshot snapshot, CancellationToken cancellationToken) - { - return _original.AddSnapshotAsync(snapshot, cancellationToken); - } - - /// - public Task GetStreamsToSnapshotAsync(string bucketId, int maxThreshold, IAsyncObserver asyncObserver, CancellationToken cancellationToken) - { - return _original.GetStreamsToSnapshotAsync(bucketId, maxThreshold, asyncObserver, cancellationToken); - } - - /// - public void Initialize() - { - _original.Initialize(); - } - - /// - public void Purge() - { - _original.Purge(); - foreach (var pipelineHook in _pipelineHooks) - { - pipelineHook.OnPurge(); - } - foreach (var pipelineHook in _pipelineHooksAsync) - { - pipelineHook.OnPurgeAsync(CancellationToken.None).GetAwaiter().GetResult(); - } - } - - /// - public void Purge(string bucketId) - { - _original.Purge(bucketId); - foreach (var pipelineHook in _pipelineHooks) - { - pipelineHook.OnPurge(bucketId); - } - foreach (var pipelineHook in _pipelineHooksAsync) - { - pipelineHook.OnPurgeAsync(bucketId, CancellationToken.None).GetAwaiter().GetResult(); - } - } - - /// - public async Task PurgeAsync(CancellationToken cancellationToken) - { - await _original.PurgeAsync(cancellationToken).ConfigureAwait(false); - foreach (var pipelineHook in _pipelineHooks) - { - pipelineHook.OnPurge(); - } - foreach (var pipelineHook in _pipelineHooksAsync) - { - await pipelineHook.OnPurgeAsync(cancellationToken).ConfigureAwait(false); - } - } - - /// - public async Task PurgeAsync(string bucketId, CancellationToken cancellationToken) - { - await _original.PurgeAsync(bucketId, cancellationToken).ConfigureAwait(false); - foreach (var pipelineHook in _pipelineHooks) - { - pipelineHook.OnPurge(bucketId); - } - foreach (var pipelineHook in _pipelineHooksAsync) - { - await pipelineHook.OnPurgeAsync(bucketId, cancellationToken).ConfigureAwait(false); - } - } - - /// - public void Drop() - { - _original.Drop(); - } - - /// - public void DeleteStream(string bucketId, string streamId) - { - _original.DeleteStream(bucketId, streamId); - foreach (var pipelineHook in _pipelineHooks) - { - pipelineHook.OnDeleteStream(bucketId, streamId); - } - foreach (var pipelineHook in _pipelineHooksAsync) - { - pipelineHook.OnDeleteStreamAsync(bucketId, streamId, CancellationToken.None).GetAwaiter().GetResult(); - } - } - - /// - public async Task DeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken) - { - await _original.DeleteStreamAsync(bucketId, streamId, cancellationToken).ConfigureAwait(false); - foreach (var pipelineHook in _pipelineHooks) - { - pipelineHook.OnDeleteStream(bucketId, streamId); - } - foreach (var pipelineHook in _pipelineHooksAsync) - { - await pipelineHook.OnDeleteStreamAsync(bucketId, streamId, cancellationToken).ConfigureAwait(false); - } - } - - /// - public bool IsDisposed - { - get { return _original.IsDisposed; } - } - - private IEnumerable ExecuteSelectCommitsHooks(IEnumerable commits) - { - foreach (var commit in commits) - { - ICommit? filtered = commit; - foreach (var hook in _pipelineHooks) - { - filtered = hook.SelectCommit(filtered); - if (filtered == null) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); - } - break; - } - } - if (filtered != null) - { - foreach (var hook in _pipelineHooksAsync) - { - filtered = hook.SelectCommitAsync(filtered, CancellationToken.None).GetAwaiter().GetResult(); - if (filtered == null) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); - } - break; - } - } - } - if (filtered == null) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.PipelineHookFilteredCommit); - } - } - else - { - yield return filtered; - } - } - } - - internal class PipelineHookObserver : IAsyncObserver - { - private readonly IEnumerable _pipelineHooks; - private readonly IEnumerable _pipelineHooksAsync; - private readonly IAsyncObserver _observer; - - public PipelineHookObserver( - IEnumerable pipelineHooks, - IEnumerable pipelineHooksAsync, - IAsyncObserver observer - ) - { - _pipelineHooks = pipelineHooks; - _pipelineHooksAsync = pipelineHooksAsync; - _observer = observer; - } - - public Task OnCompletedAsync(CancellationToken cancellationToken) - { - return _observer.OnCompletedAsync(cancellationToken); - } - - public Task OnErrorAsync(Exception error, CancellationToken cancellationToken) - { - return _observer.OnErrorAsync(error, cancellationToken); - } - - public async Task OnNextAsync(ICommit value, CancellationToken cancellationToken) - { - var commit = await ExecuteSelectCommitHooksAsync(value, cancellationToken).ConfigureAwait(false); - if (commit != null) - { - return await _observer.OnNextAsync(commit, cancellationToken).ConfigureAwait(false); - } - return true; - } - - private async Task ExecuteSelectCommitHooksAsync(ICommit commit, CancellationToken cancellationToken) - { - ICommit? filtered = commit; - foreach (var hook in _pipelineHooks) - { - filtered = hook.SelectCommit(filtered); - if (filtered == null) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); - } - break; - } - } - if (filtered != null) - { - foreach (var hook in _pipelineHooksAsync) - { - filtered = await hook.SelectCommitAsync(filtered, cancellationToken).ConfigureAwait(false); - if (filtered == null) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.PipelineHookSkippedCommit, hook.GetType(), commit.CommitId); - } - break; - } - } - } - if (filtered == null) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.PipelineHookFilteredCommit); - } - return null; - } - else - { - return filtered; - } - } - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/StorageException.cs b/src/NEventStore/Persistence/StorageException.cs deleted file mode 100644 index 80e9db502..000000000 --- a/src/NEventStore/Persistence/StorageException.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NEventStore.Persistence -{ - /// - /// Represents a general failure of the storage engine or persistence infrastructure. - /// - public class StorageException : Exception - { - /// - /// Initializes a new instance of the StorageException class. - /// - public StorageException() - { } - - /// - /// Initializes a new instance of the StorageException class. - /// - /// The message that describes the error. - public StorageException(string message) - : base(message) - { } - - /// - /// Initializes a new instance of the StorageException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public StorageException(string message, Exception innerException) - : base(message, innerException) - { } - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/StorageUnavailableException.cs b/src/NEventStore/Persistence/StorageUnavailableException.cs deleted file mode 100644 index a8470c9b2..000000000 --- a/src/NEventStore/Persistence/StorageUnavailableException.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NEventStore.Persistence -{ - /// - /// Indicates that the underlying persistence medium is unavailable or offline. - /// - public class StorageUnavailableException : StorageException - { - /// - /// Initializes a new instance of the StorageUnavailableException class. - /// - public StorageUnavailableException() - {} - - /// - /// Initializes a new instance of the StorageUnavailableException class. - /// - /// The message that describes the error. - public StorageUnavailableException(string message) - : base(message) - {} - - /// - /// Initializes a new instance of the StorageUnavailableException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public StorageUnavailableException(string message, Exception innerException) - : base(message, innerException) - {} - } -} \ No newline at end of file diff --git a/src/NEventStore/Persistence/StreamHead.cs b/src/NEventStore/Persistence/StreamHead.cs deleted file mode 100644 index 35d075a31..000000000 --- a/src/NEventStore/Persistence/StreamHead.cs +++ /dev/null @@ -1,72 +0,0 @@ -#pragma warning disable RCS1170 // Use read-only auto-implemented property. - -namespace NEventStore.Persistence -{ - /// - /// Indicates the most recent information representing the head of a given stream. - /// - public class StreamHead : IStreamHead - { - /// - /// Initializes a new instance of the StreamHead class. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The value which uniquely identifies the stream in the bucket where the last snapshot exceeds the allowed threshold. - /// The value which indicates the revision, length, or number of events committed to the stream. - /// The value which indicates the revision at which the last snapshot was taken. - public StreamHead(string bucketId, string streamId, int headRevision, int snapshotRevision) - { - BucketId = bucketId; - StreamId = streamId; - HeadRevision = headRevision; - SnapshotRevision = snapshotRevision; - } - - /// - /// Stream Head Equality Comparer - /// - public static IEqualityComparer StreamIdBucketIdComparer { get; } = new StreamHeadEqualityComparer(); - - /// - /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. - /// - public string BucketId { get; private set; } - - /// - /// Gets the value which uniquely identifies the stream where the last snapshot exceeds the allowed threshold. - /// - public string StreamId { get; private set; } - - /// - /// Gets the value which indicates the revision, length, or number of events committed to the stream. - /// - public int HeadRevision { get; private set; } - - /// - /// Gets the value which indicates the revision at which the last snapshot was taken. - /// - public int SnapshotRevision { get; private set; } - - /// - /// Determines whether the specified object is equal to the current object. - /// - /// The object to compare with the current object. - /// If the two objects are equal, returns true; otherwise false. - public override bool Equals(object? obj) - { - return obj is StreamHead commit - && commit.StreamId == StreamId; - } - - /// - /// Returns the hash code for this instance. - /// - /// The hash code for this instance. - public override int GetHashCode() - { - return StreamId.GetHashCode(); - } - } -} - -#pragma warning restore RCS1170 // Use read-only auto-implemented property. \ No newline at end of file diff --git a/src/NEventStore/Persistence/StreamHeadEqualityComparer.cs b/src/NEventStore/Persistence/StreamHeadEqualityComparer.cs deleted file mode 100644 index 8c2326a2d..000000000 --- a/src/NEventStore/Persistence/StreamHeadEqualityComparer.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NEventStore.Persistence { - /// - /// Represents a strategy for comparing stream heads. - /// - public sealed class StreamHeadEqualityComparer : IEqualityComparer { - /// - public bool Equals(IStreamHead? x, IStreamHead? y) { - if (ReferenceEquals(x, y)) { - return true; - } - if (x is null) { - return false; - } - if (y is null) { - return false; - } - if (x.GetType() != y.GetType()) { - return false; - } - return string.Equals(x.StreamId, y.StreamId, StringComparison.Ordinal) - && string.Equals(x.BucketId, y.BucketId, StringComparison.Ordinal); - } - - /// - public int GetHashCode(IStreamHead obj) { - unchecked { - return ((obj.StreamId?.GetHashCode() ?? 0) * 397) ^ (obj.BucketId?.GetHashCode() ?? 0); - } - } - } -} \ No newline at end of file diff --git a/src/NEventStore/PersistenceWireup.cs b/src/NEventStore/PersistenceWireup.cs deleted file mode 100644 index 4988d20cf..000000000 --- a/src/NEventStore/PersistenceWireup.cs +++ /dev/null @@ -1,144 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Diagnostics; -using NEventStore.Logging; -using NEventStore.Persistence; -using NEventStore.Serialization; - -namespace NEventStore -{ - /// - /// Represents the persistence wireup. - /// - public class PersistenceWireup : Wireup - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(PersistenceWireup)); - private bool _initialize; -#if NET462 - private bool _tracking; - private string? _trackingInstanceName; -#endif - - /// - /// Initializes a new instance of the PersistenceWireup class. - /// - public PersistenceWireup(Wireup inner) - : base(inner) - { -#pragma warning disable S125 // Sections of code should not be commented out - /* EnlistInAmbientTransaction: Will be moved to the specific Persistence driver or completely removed letting the clients handle that - Container.Register(TransactionScopeOption.Suppress); - */ -#pragma warning restore S125 // Sections of code should not be commented out - } - - /// - /// Configures the persistence engine to use the specified serializer. - /// - public virtual PersistenceWireup WithPersistence(IPersistStreams instance) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Messages.RegisteringPersistenceEngine, instance.GetType()); - } - Register(instance); - return this; - } - - /// - /// Configures the persistence engine to use the specified serializer. - /// - protected virtual SerializationWireup WithSerializer(ISerialize serializer) - { - return new SerializationWireup(this, serializer); - } - - /// - /// Initializes the storage engine. - /// - public virtual PersistenceWireup InitializeStorageEngine() - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Messages.ConfiguringEngineInitialization); - } - _initialize = true; - return this; - } - -#if NET462 - /// - /// Configures the persistence engine to track performance counters. - /// - /// - public virtual PersistenceWireup TrackPerformanceInstance(string instanceName) - { - _tracking = true; - _trackingInstanceName = instanceName - ?? throw new ArgumentNullException(nameof(instanceName), Messages.InstanceCannotBeNull); - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Messages.ConfiguringEnginePerformanceTracking); - } - return this; - } -#endif - -#pragma warning disable S125 // Sections of code should not be commented out - /* EnlistInAmbientTransaction: Will be moved to the specific Persistence driver or completely removed letting the clients handle that - /// - /// Enables two-phase commit. - /// By default NEventStore will suppress surrounding TransactionScopes - /// (All the Persistence drivers that support transactions will create a - /// private nested TransactionScope with for each operation) - /// so that all of its operations run in a dedicated, separate transaction. - /// This option changes the behavior so that NEventStore enlists in a surrounding TransactionScope, - /// if there is any (All the Persistence drivers that support transactions will create a - /// private nested TransactionScope with for each operation). - /// - /// - /// Enabling the two-phase commit will also disable the - /// that provide some additionl concurrency checks to avoid useless roundtrips to the databases. - /// - /// - public virtual PersistenceWireup EnlistInAmbientTransaction() - { - if (Logger.IsInfoEnabled) Logger.Info(Messages.ConfiguringEngineEnlistment); - Container.Register(TransactionScopeOption.Required); - return this; - } - */ -#pragma warning restore S125 // Sections of code should not be commented out - - /// - /// Builds the persistence engine. - /// - public override IStoreEvents Build() - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Messages.BuildingEngine); - } - - var engine = Container.Resolve() - ?? throw new InvalidOperationException("IPersistStreams not registered"); - - if (_initialize) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.InitializingEngine); - } - engine.Initialize(); - } - -#if NET462 - if (_tracking) - { - Container.Register(new PerformanceCounterPersistenceEngine(engine, _trackingInstanceName!)); - } -#endif - return base.Build(); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/PersistenceWireupExtensions.cs b/src/NEventStore/PersistenceWireupExtensions.cs deleted file mode 100644 index 3a17e4342..000000000 --- a/src/NEventStore/PersistenceWireupExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using NEventStore.Logging; -using Microsoft.Extensions.Logging; -using NEventStore.Persistence; -using NEventStore.Persistence.InMemory; - -namespace NEventStore -{ - /// - /// Persistence wireup extensions. - /// - public static class PersistenceWireupExtensions - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(OptimisticPipelineHook)); - - /// - /// Configures the persistence engine to use the in-memory persistence engine. - /// - public static PersistenceWireup UsingInMemoryPersistence(this Wireup wireup) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.WireupSetPersistenceEngine, "InMemoryPersistenceEngine"); - } - wireup.Register(new InMemoryPersistenceEngine()); - - return new PersistenceWireup(wireup); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/PipelineHookAsyncBase.cs b/src/NEventStore/PipelineHookAsyncBase.cs deleted file mode 100644 index 1e33f80b5..000000000 --- a/src/NEventStore/PipelineHookAsyncBase.cs +++ /dev/null @@ -1,52 +0,0 @@ - -namespace NEventStore -{ - /// - /// Provides a base implementation of the interface. - /// - public abstract class PipelineHookAsyncBase : IPipelineHookAsync - { - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - // Cleanup - } - - /// - public virtual Task OnDeleteStreamAsync(string bucketId, string streamId, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - /// - public virtual Task OnPurgeAsync(string? bucketId, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - /// - public virtual Task PostCommitAsync(ICommit committed, CancellationToken cancellationToken) - { - return Task.CompletedTask; - } - - /// - public virtual Task PreCommitAsync(CommitAttempt attempt, CancellationToken cancellationToken) - { - return Task.FromResult(true); - } - - /// - public virtual Task SelectCommitAsync(ICommit committed, CancellationToken cancellationToken) - { - return Task.FromResult(committed); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/PipelineHookBase.cs b/src/NEventStore/PipelineHookBase.cs deleted file mode 100644 index 350746c0f..000000000 --- a/src/NEventStore/PipelineHookBase.cs +++ /dev/null @@ -1,46 +0,0 @@ - -namespace NEventStore -{ - /// - /// Provides a base implementation of the interface. - /// - public abstract class PipelineHookBase : IPipelineHook - { - /// - public virtual ICommit? SelectCommit(ICommit committed) - { - return committed; - } - - /// - public virtual bool PreCommit(CommitAttempt attempt) - { - return true; - } - - /// - public virtual void PostCommit(ICommit committed) - { } - - /// - public virtual void OnPurge(string? bucketId) - { } - - /// - public virtual void OnDeleteStream(string bucketId, string streamId) - { } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - protected virtual void Dispose(bool disposing) - { - // Cleanup - } - } -} \ No newline at end of file diff --git a/src/NEventStore/PipelineHookExtensions.cs b/src/NEventStore/PipelineHookExtensions.cs deleted file mode 100644 index 9f351fd31..000000000 --- a/src/NEventStore/PipelineHookExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace NEventStore -{ - /// - /// Provides extension methods for . - /// - public static class PipelineHookExtensions - { - /// - /// Invoked when all buckets have been purged. - /// - /// The pipeline hook. - public static void OnPurge(this IPipelineHook pipelineHook) - { - pipelineHook.OnPurge(null); - } - - /// - /// Invoked when all buckets have been purged. - /// - /// The pipeline hook. - /// The token to monitor for cancellation requests. - public static Task OnPurgeAsync(this IPipelineHookAsync pipelineHook, CancellationToken cancellationToken) - { - return pipelineHook.OnPurgeAsync(null, cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Properties/ProjectAssemblyInfo.cs b/src/NEventStore/Properties/ProjectAssemblyInfo.cs deleted file mode 100644 index 94ee3b119..000000000 --- a/src/NEventStore/Properties/ProjectAssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("NEventStore")] -[assembly: AssemblyDescription("")] -[assembly: Guid("9cb4668f-d7b2-4ad9-8f9b-2af9903d2db2")] \ No newline at end of file diff --git a/src/NEventStore/Resources.Designer.cs b/src/NEventStore/Resources.Designer.cs deleted file mode 100644 index 53e1afe31..000000000 --- a/src/NEventStore/Resources.Designer.cs +++ /dev/null @@ -1,531 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NEventStore { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Adding commit '{0}' with {1} events to stream '{2}' bucket'{3}'.. - /// - internal static string AddingCommitsToStream { - get { - return ResourceManager.GetString("AddingCommitsToStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Adding a snapshot for bucket '{0}' stream '{1}' at revision '{2}'.. - /// - internal static string AddingSnapshot { - get { - return ResourceManager.GetString("AddingSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The object has already been disposed.. - /// - internal static string AlreadyDisposed { - get { - return ResourceManager.GetString("AlreadyDisposed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Appending uncommitted event '{0}' to stream '{1}' bucket '{2}'. - /// - internal static string AppendingUncommittedToStream { - get { - return ResourceManager.GetString("AppendingUncommittedToStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to append commit '{0}' to stream '{1}' bucket '{2}' at position '{3}'.. - /// - internal static string AttemptingToCommit { - get { - return ResourceManager.GetString("AttemptingToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Attempting to commit all changes on stream '{0}' bucket '{1}' to the underlying store.. - /// - internal static string AttemptingToCommitChanges { - get { - return ResourceManager.GetString("AttemptingToCommitChanges", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Building a commit attempt '{0}' on stream '{1}' bucket '{2}'.. - /// - internal static string BuildingCommitAttempt { - get { - return ResourceManager.GetString("BuildingCommitAttempt", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The stream '{0}' bucket '{1}' was partially loaded up to revision {1}. Cannot append commits to a partially loaded stream, refreshing the stream.. - /// - internal static string CannotAddCommitsToPartiallyLoadedStream { - get { - return ResourceManager.GetString("CannotAddCommitsToPartiallyLoadedStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Clearing all uncommitted changes on stream '{0}' bucket '{1}'.. - /// - internal static string ClearingUncommittedChanges { - get { - return ResourceManager.GetString("ClearingUncommittedChanges", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pipeline hook of type '{0}' rejected attempt '{1}'.. - /// - internal static string CommitRejectedByPipelineHook { - get { - return ResourceManager.GetString("CommitRejectedByPipelineHook", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Committing attempt '{0}' which contains {1} events to the underlying persistence engine.. - /// - internal static string CommittingAttempt { - get { - return ResourceManager.GetString("CommittingAttempt", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Converting an Event from '{0}' to '{1}'.. - /// - internal static string ConvertingEvent { - get { - return ResourceManager.GetString("ConvertingEvent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Creating stream '{0}' in bucket '{1}'.. - /// - internal static string CreatingStream { - get { - return ResourceManager.GetString("CreatingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Deleting stream '{0}' from bucket '{1}'.. - /// - internal static string DeletingStream { - get { - return ResourceManager.GetString("DeletingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Disposing engine.. - /// - internal static string DisposingEngine { - get { - return ResourceManager.GetString("DisposingEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from bucket '{0}' since checkpoint '{1}' (excluded).. - /// - internal static string GettingAllCommitsFromBucketAndCheckpoint { - get { - return ResourceManager.GetString("GettingAllCommitsFromBucketAndCheckpoint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits since checkpoint '{0}' (excluded).. - /// - internal static string GettingAllCommitsFromCheckpoint { - get { - return ResourceManager.GetString("GettingAllCommitsFromCheckpoint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits for stream '{0}' bucket '{1}' between '{2}' and '{3}'.. - /// - internal static string GettingAllCommitsFromRevision { - get { - return ResourceManager.GetString("GettingAllCommitsFromRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from bucket '{0}' from '{1}' forward.. - /// - internal static string GettingAllCommitsFromTime { - get { - return ResourceManager.GetString("GettingAllCommitsFromTime", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from bucket '{0}' from '{1}' to '{2}'.. - /// - internal static string GettingAllCommitsFromToTime { - get { - return ResourceManager.GetString("GettingAllCommitsFromToTime", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from bucket '{0}' from checkpoint '{1}' (excluded) up to '{2}' (included).. - /// - internal static string GettingCommitsFromBucketAndFromToCheckpoint { - get { - return ResourceManager.GetString("GettingCommitsFromBucketAndFromToCheckpoint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting all commits from checkpoint '{0}' (excluded) up to '{1}' (included).. - /// - internal static string GettingCommitsFromToCheckpoint { - get { - return ResourceManager.GetString("GettingCommitsFromToCheckpoint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting the most recent snapshot from bucket '{0}' for stream '{1}' on/since revision '{2}'.. - /// - internal static string GettingSnapshotForStream { - get { - return ResourceManager.GetString("GettingSnapshotForStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Getting the set of all streams to be snapshot from bucket '{0}' which exceed {1} revisions without a snapshot.. - /// - internal static string GettingStreamsToSnapshot { - get { - return ResourceManager.GetString("GettingStreamsToSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ignoring some events on commit '{0}' of stream '{1}' because they starting before revision {2}.. - /// - internal static string IgnoringBeforeRevision { - get { - return ResourceManager.GetString("IgnoringBeforeRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ignoring some events on commit '{0}' of stream '{1}' because they go beyond revision {2}.. - /// - internal static string IgnoringBeyondRevision { - get { - return ResourceManager.GetString("IgnoringBeyondRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing engine.. - /// - internal static string InitializingEngine { - get { - return ResourceManager.GetString("InitializingEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing persistence engine.. - /// - internal static string InitializingPersistence { - get { - return ResourceManager.GetString("InitializingPersistence", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pushing commit '{0}' to post-commit hook of type '{1}'.. - /// - internal static string InvokingPostCommitPipelineHooks { - get { - return ResourceManager.GetString("InvokingPostCommitPipelineHooks", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pushing commit '{0}' to pre-commit hook of type '{1}'.. - /// - internal static string InvokingPreCommitHooks { - get { - return ResourceManager.GetString("InvokingPreCommitHooks", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There are no outstanding changes to be committed stream '{0}' bucket '{1}'.. - /// - internal static string NoChangesToCommit { - get { - return ResourceManager.GetString("NoChangesToCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No other commits have been discovered that conflict for stream '{0}' bucket '{1}'.. - /// - internal static string NoConflicts { - get { - return ResourceManager.GetString("NoConflicts", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Purging all commits on stream '{0}' bucket '{1}' from tracking.. - /// - internal static string NoLongerTrackingStream { - get { - return ResourceManager.GetString("NoLongerTrackingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The stream revision must be a positive number.. - /// - internal static string NonPositiveRevisionNumber { - get { - return ResourceManager.GetString("NonPositiveRevisionNumber", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The commit sequence must be a positive number.. - /// - internal static string NonPositiveSequenceNumber { - get { - return ResourceManager.GetString("NonPositiveSequenceNumber", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Opening stream '{0}' from bucket '{1}' between revisions {2} and {3}.. - /// - internal static string OpeningStreamAtRevision { - get { - return ResourceManager.GetString("OpeningStreamAtRevision", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Opening stream '{0}' from bucket '{1}' with snapshot at {2} up to revision {3}.. - /// - internal static string OpeningStreamWithSnapshot { - get { - return ResourceManager.GetString("OpeningStreamWithSnapshot", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Verifying that no other commits have succeed on the stream '{0}'.. - /// - internal static string OptimisticConcurrencyCheck { - get { - return ResourceManager.GetString("OptimisticConcurrencyCheck", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pushing attempt '{0}' on stream '{1}' bucket '{2}' with '{3}' events to the underlying store.. - /// - internal static string PersistingCommit { - get { - return ResourceManager.GetString("PersistingCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to One or more pipeline hooks filtered out the commit.. - /// - internal static string PipelineHookFilteredCommit { - get { - return ResourceManager.GetString("PipelineHookFilteredCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Pipeline hook of type '{0}' skipped over commit '{1}'.. - /// - internal static string PipelineHookSkippedCommit { - get { - return ResourceManager.GetString("PipelineHookSkippedCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Purging all data from storage.. - /// - internal static string PurgingStore { - get { - return ResourceManager.GetString("PurgingStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The collection is read only and cannot be modified.. - /// - internal static string ReadOnlyCollection { - get { - return ResourceManager.GetString("ReadOnlyCollection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The stream revision must always be greater than or equal to the commit sequence.. - /// - internal static string RevisionTooSmall { - get { - return ResourceManager.GetString("RevisionTooSmall", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Shutting down event store.. - /// - internal static string ShuttingDownStore { - get { - return ResourceManager.GetString("ShuttingDownStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Tracking commit {0} on stream '{1}' bucket '{2}'.. - /// - internal static string TrackingCommit { - get { - return ResourceManager.GetString("TrackingCommit", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Tracking up to {0} streams.. - /// - internal static string TrackingStreams { - get { - return ResourceManager.GetString("TrackingStreams", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The underlying stream '{0}' bucket '{1}' has changed since the last known commit, refreshing the stream. Exception Message: {2}. - /// - internal static string UnderlyingStreamHasChanged { - get { - return ResourceManager.GetString("UnderlyingStreamHasChanged", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Updating stream head for stream '{0}' bucket '{1}'.. - /// - internal static string UpdatingStreamHead { - get { - return ResourceManager.GetString("UpdatingStreamHead", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Hook into pipeline with hooks: {0}. - /// - internal static string WireupHookIntoPipeline { - get { - return ResourceManager.GetString("WireupHookIntoPipeline", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configured Persistence Engine: {0}. - /// - internal static string WireupSetPersistenceEngine { - get { - return ResourceManager.GetString("WireupSetPersistenceEngine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configured serializer: {0}. - /// - internal static string WireupSetSerializer { - get { - return ResourceManager.GetString("WireupSetSerializer", resourceCulture); - } - } - } -} diff --git a/src/NEventStore/Resources.resx b/src/NEventStore/Resources.resx deleted file mode 100644 index 4e39441c4..000000000 --- a/src/NEventStore/Resources.resx +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The collection is read only and cannot be modified. - - - The object has already been disposed. - - - The commit sequence must be a positive number. - - - The stream revision must be a positive number. - - - The stream revision must always be greater than or equal to the commit sequence. - - - Initializing persistence engine. - - - Disposing engine. - - - Initializing engine. - - - Getting all commits for stream '{0}' bucket '{1}' between '{2}' and '{3}'. - - - Getting all commits from bucket '{0}' from '{1}' forward. - - - Getting all commits from bucket '{0}' from '{1}' to '{2}'. - - - Attempting to append commit '{0}' to stream '{1}' bucket '{2}' at position '{3}'. - - - Updating stream head for stream '{0}' bucket '{1}'. - - - Getting the set of all streams to be snapshot from bucket '{0}' which exceed {1} revisions without a snapshot. - - - Getting the most recent snapshot from bucket '{0}' for stream '{1}' on/since revision '{2}'. - - - Adding a snapshot for bucket '{0}' stream '{1}' at revision '{2}'. - - - Purging all data from storage. - - - Tracking up to {0} streams. - - - Verifying that no other commits have succeed on the stream '{0}'. - - - No other commits have been discovered that conflict for stream '{0}' bucket '{1}'. - - - Tracking commit {0} on stream '{1}' bucket '{2}'. - - - Purging all commits on stream '{0}' bucket '{1}' from tracking. - - - Creating stream '{0}' in bucket '{1}'. - - - Opening stream '{0}' from bucket '{1}' between revisions {2} and {3}. - - - Opening stream '{0}' from bucket '{1}' with snapshot at {2} up to revision {3}. - - - Pipeline hook of type '{0}' skipped over commit '{1}'. - - - One or more pipeline hooks filtered out the commit. - - - Pushing commit '{0}' to pre-commit hook of type '{1}'. - - - Pipeline hook of type '{0}' rejected attempt '{1}'. - - - Committing attempt '{0}' which contains {1} events to the underlying persistence engine. - - - Pushing commit '{0}' to post-commit hook of type '{1}'. - - - Adding commit '{0}' with {1} events to stream '{2}' bucket'{3}'. - - - Ignoring some events on commit '{0}' of stream '{1}' because they go beyond revision {2}. - - - Ignoring some events on commit '{0}' of stream '{1}' because they starting before revision {2}. - - - Appending uncommitted event '{0}' to stream '{1}' bucket '{2}' - - - Attempting to commit all changes on stream '{0}' bucket '{1}' to the underlying store. - - - There are no outstanding changes to be committed stream '{0}' bucket '{1}'. - - - The underlying stream '{0}' bucket '{1}' has changed since the last known commit, refreshing the stream. Exception Message: {2} - - - Pushing attempt '{0}' on stream '{1}' bucket '{2}' with '{3}' events to the underlying store. - - - Building a commit attempt '{0}' on stream '{1}' bucket '{2}'. - - - Clearing all uncommitted changes on stream '{0}' bucket '{1}'. - - - Shutting down event store. - - - Converting an Event from '{0}' to '{1}'. - - - Getting all commits since checkpoint '{0}' (excluded). - - - Getting all commits from bucket '{0}' since checkpoint '{1}' (excluded). - - - Deleting stream '{0}' from bucket '{1}'. - - - Hook into pipeline with hooks: {0} - - - Configured Persistence Engine: {0} - - - Configured serializer: {0} - - - The stream '{0}' bucket '{1}' was partially loaded up to revision {1}. Cannot append commits to a partially loaded stream, refreshing the stream. - - - Getting all commits from bucket '{0}' from checkpoint '{1}' (excluded) up to '{2}' (included). - - - Getting all commits from checkpoint '{0}' (excluded) up to '{1}' (included). - - \ No newline at end of file diff --git a/src/NEventStore/Serialization/ByteStreamDocumentSerializer.cs b/src/NEventStore/Serialization/ByteStreamDocumentSerializer.cs deleted file mode 100644 index e001e03fc..000000000 --- a/src/NEventStore/Serialization/ByteStreamDocumentSerializer.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore.Serialization -{ - /// - /// A document serializer that uses a serializer to serialize and deserialize objects to and from byte arrays. - /// - public class ByteStreamDocumentSerializer : IDocumentSerializer - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(ByteStreamDocumentSerializer)); - private readonly ISerialize _serializer; - - /// - /// Initializes a new instance of the ByteStreamDocumentSerializer class. - /// - public ByteStreamDocumentSerializer(ISerialize serializer) - { - _serializer = serializer; - } - - /// - /// Serializes the object graph in a byte array - public object Serialize(T graph) where T : notnull - { - if (graph == null) - { - throw new ArgumentNullException(nameof(graph)); - } - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.SerializingGraph, typeof(T)); - } - return _serializer.Serialize(graph); - } - - /// - /// - /// Accepts a byte array (in the form of a byte array or a base64 encoded string) - /// and deserialize it to an object graph. - /// - public T? Deserialize(object document) - { - var bytes = (FromBase64(document as string) ?? document as byte[]) - ?? throw new NotSupportedException("document must be byte[] or a string representing base64 encoded byte[]"); - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.DeserializingStream, typeof(T)); - } - return _serializer.Deserialize(bytes); - } - - private static byte[]? FromBase64(string? value) - { - if (string.IsNullOrEmpty(value)) - { - return null; - } - - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.InspectingTextStream); - } - - try - { - return Convert.FromBase64String(value); - } - catch (FormatException) - { - return null; - } - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Serialization/DocumentObjectSerializer.cs b/src/NEventStore/Serialization/DocumentObjectSerializer.cs deleted file mode 100644 index d050849fe..000000000 --- a/src/NEventStore/Serialization/DocumentObjectSerializer.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace NEventStore.Serialization -{ - /// - /// A simple serializer that does not perform any serialization. - /// - public class DocumentObjectSerializer : IDocumentSerializer - { - /// - public object Serialize(T graph) where T : notnull - { - if (graph == null) - { - throw new ArgumentNullException(nameof(graph)); - } - return graph; - } - - /// - public T? Deserialize(object document) - { - return (T?)document; - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Serialization/GzipSerializer.cs b/src/NEventStore/Serialization/GzipSerializer.cs deleted file mode 100644 index d2dd6edcd..000000000 --- a/src/NEventStore/Serialization/GzipSerializer.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.IO.Compression; -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore.Serialization -{ - /// - /// Represents a serializer that compresses the serialized object using GZip. - /// - public class GzipSerializer : ISerialize - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(GzipSerializer)); - private readonly ISerialize _inner; - - /// - /// Initializes a new instance of the GzipSerializer class. - /// - public GzipSerializer(ISerialize inner) - { - _inner = inner; - } - - /// - public virtual void Serialize(Stream output, T graph) where T : notnull - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.SerializingGraph, typeof(T)); - } - using var compress = new DeflateStream(output, CompressionMode.Compress, true); - _inner.Serialize(compress, graph); - } - - /// - public virtual T? Deserialize(Stream input) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.DeserializingStream, typeof(T)); - } - using var decompress = new DeflateStream(input, CompressionMode.Decompress, true); - return _inner.Deserialize(decompress); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Serialization/IDocumentSerializer.cs b/src/NEventStore/Serialization/IDocumentSerializer.cs deleted file mode 100644 index 74c51782d..000000000 --- a/src/NEventStore/Serialization/IDocumentSerializer.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace NEventStore.Serialization -{ - /// - /// Provides the ability to serialize an object graph to and from a document. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface IDocumentSerializer - { - /// - /// Serializes the object graph provided into a document. - /// - /// The type of object to be serialized - /// The object graph to be serialized. - /// The document form of the graph provided. - object Serialize(T graph) where T : notnull; - - /// - /// Deserializes the document provided into an object graph. - /// - /// The type of object graph. - /// The document to be deserialized. - /// An object graph of the specified type. - T? Deserialize(object document); - } -} \ No newline at end of file diff --git a/src/NEventStore/Serialization/ISerialize.cs b/src/NEventStore/Serialization/ISerialize.cs deleted file mode 100644 index ba00cf418..000000000 --- a/src/NEventStore/Serialization/ISerialize.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace NEventStore.Serialization -{ - using System.IO; - - /// - /// Provides the ability to serialize and deserialize an object graph. - /// - /// - /// Instances of this class must be designed to be multi-thread safe such that they can be shared between threads. - /// - public interface ISerialize - { - /// - /// Serializes the object graph provided and writes a serialized representation to the output stream provided. - /// - /// The type of object to be serialized - /// The stream into which the serialized object graph should be written. - /// The object graph to be serialized. - void Serialize(Stream output, T graph) where T : notnull; - - /// - /// Deserializes the stream provided and reconstructs the corresponding object graph. - /// - /// The type of object to be deserialized. - /// The stream of bytes from which the object will be reconstructed. - /// The reconstructed object. - T? Deserialize(Stream input); - } -} \ No newline at end of file diff --git a/src/NEventStore/Serialization/Messages.Designer.cs b/src/NEventStore/Serialization/Messages.Designer.cs deleted file mode 100644 index 5bd835077..000000000 --- a/src/NEventStore/Serialization/Messages.Designer.cs +++ /dev/null @@ -1,99 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NEventStore.Serialization { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Messages { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Messages() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NEventStore.Serialization.Messages", typeof(Messages).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Deserializing stream to object of type '{0}'.. - /// - internal static string DeserializingStream { - get { - return ResourceManager.GetString("DeserializingStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Inspecting text-based stream contents.. - /// - internal static string InspectingTextStream { - get { - return ResourceManager.GetString("InspectingTextStream", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The encryption key must be exactly 16 bytes.. - /// - internal static string InvalidKeyLength { - get { - return ResourceManager.GetString("InvalidKeyLength", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Serializing object graph of type '{0}'.. - /// - internal static string SerializingGraph { - get { - return ResourceManager.GetString("SerializingGraph", resourceCulture); - } - } - } -} diff --git a/src/NEventStore/Serialization/Messages.resx b/src/NEventStore/Serialization/Messages.resx deleted file mode 100644 index 87a2e1f68..000000000 --- a/src/NEventStore/Serialization/Messages.resx +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Deserializing stream to object of type '{0}'. - - - Serializing object graph of type '{0}'. - - - The encryption key must be exactly 16 bytes. - - - Inspecting text-based stream contents. - - \ No newline at end of file diff --git a/src/NEventStore/Serialization/NonDisposableStream.cs b/src/NEventStore/Serialization/NonDisposableStream.cs deleted file mode 100644 index 4d9ffaf7a..000000000 --- a/src/NEventStore/Serialization/NonDisposableStream.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace NEventStore.Serialization -{ - using System.IO; - - /// - /// Represents a stream that wraps another stream and prevents it from being disposed. - /// - internal class NonDisposableStream : Stream - { - private readonly Stream _stream; - - /// - /// Initializes a new instance of the NonDisposableStream class. - /// - public NonDisposableStream(Stream stream) - { - _stream = stream; - } - - public override bool CanRead - { - get { return _stream.CanRead; } - } - - public override bool CanSeek - { - get { return _stream.CanSeek; } - } - - public override bool CanWrite - { - get { return _stream.CanWrite; } - } - - public override long Length - { - get { return _stream.Length; } - } - - public override long Position - { - get { return _stream.Position; } - set { _stream.Position = value; } - } - -#pragma warning disable CA2215 // Dispose methods should call base class dispose - protected override void Dispose(bool disposing) -#pragma warning restore CA2215 // Dispose methods should call base class dispose - { - // no-op - } - - public override void Close() - { - // no-op - } - - public override void Flush() - { - _stream.Flush(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - return _stream.Seek(offset, origin); - } - - public override void SetLength(long value) - { - _stream.SetLength(value); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return _stream.Read(buffer, offset, count); - } - - public override void Write(byte[] buffer, int offset, int count) - { - _stream.Write(buffer, offset, count); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Serialization/RijndaelSerializer.cs b/src/NEventStore/Serialization/RijndaelSerializer.cs deleted file mode 100644 index 8164316d6..000000000 --- a/src/NEventStore/Serialization/RijndaelSerializer.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Collections; -using System.Security.Cryptography; -using Microsoft.Extensions.Logging; -using NEventStore.Logging; - -namespace NEventStore.Serialization -{ - /// - /// Represents a serializer that encrypts the serialized data using the Rijndael algorithm. - /// - public class RijndaelSerializer : ISerialize - { - private const int KeyLength = 16; // bytes - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(RijndaelSerializer)); - private readonly byte[] _encryptionKey; - private readonly ISerialize _inner; - - /// - /// Initializes a new instance of the RijndaelSerializer class. - /// - /// - public RijndaelSerializer(ISerialize inner, byte[] encryptionKey) - { - if (!KeyIsValid(encryptionKey, KeyLength)) - { - throw new ArgumentException(Messages.InvalidKeyLength, nameof(encryptionKey)); - } - - _encryptionKey = encryptionKey; - _inner = inner; - } - - /// - public virtual void Serialize(Stream output, T graph) where T: notnull - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.SerializingGraph, typeof(T)); - } - - using var rijndael = new RijndaelManaged(); - rijndael.Key = _encryptionKey; - rijndael.Mode = CipherMode.CBC; - rijndael.GenerateIV(); - - using ICryptoTransform encryptor = rijndael.CreateEncryptor(); - using var wrappedOutput = new NonDisposableStream(output); - using var encryptionStream = new CryptoStream(wrappedOutput, encryptor, CryptoStreamMode.Write); - wrappedOutput.Write(rijndael.IV, 0, rijndael.IV.Length); - _inner.Serialize(encryptionStream, graph); - encryptionStream.Flush(); - encryptionStream.FlushFinalBlock(); - } - - /// - public virtual T? Deserialize(Stream input) - { - if (Logger.IsEnabled(LogLevel.Trace)) - { - Logger.LogTrace(Messages.DeserializingStream, typeof(T)); - } - - using var rijndael = new RijndaelManaged(); - rijndael.Key = _encryptionKey; - rijndael.IV = GetInitVectorFromStream(input, rijndael.IV.Length); - rijndael.Mode = CipherMode.CBC; - - using ICryptoTransform decrypter = rijndael.CreateDecryptor(); - using var decryptedStream = new CryptoStream(input, decrypter, CryptoStreamMode.Read); - return _inner.Deserialize(decryptedStream); - } - - private static bool KeyIsValid(ICollection key, int length) - { - return key != null && key.Count == length; - } - - private static byte[] GetInitVectorFromStream(Stream encrypted, int initVectorSizeInBytes) - { - var buffer = new byte[initVectorSizeInBytes]; - // The encrypted payload starts with the raw IV bytes. Consume that prefix completely - // before constructing the decrypting CryptoStream so the inner serializer always reads - // from decrypted payload bytes only, even when the underlying stream satisfies reads - // in smaller chunks than requested. - ReadExact(encrypted, buffer, initVectorSizeInBytes); - return buffer; - } - - private static void ReadExact(Stream stream, byte[] buffer, int count) - { - int offset = 0; - while (offset < count) - { - int read = stream.Read(buffer, offset, count - offset); - if (read == 0) - { - throw new EndOfStreamException("Encrypted stream ended before the initialization vector was fully read."); - } - - offset += read; - } - } - } -} diff --git a/src/NEventStore/Serialization/SerializationExtensions.cs b/src/NEventStore/Serialization/SerializationExtensions.cs deleted file mode 100644 index bb95b12d6..000000000 --- a/src/NEventStore/Serialization/SerializationExtensions.cs +++ /dev/null @@ -1,180 +0,0 @@ - -#if NET8_0_OR_GREATER -using System.Buffers; -#endif - -namespace NEventStore.Serialization -{ - /// - /// Implements extension methods that make call to the serialization infrastructure more simple. - /// - public static class SerializationExtensions - { - /// - /// Serializes the object provided. - /// - /// The type of object to be serialized - /// The serializer to use. - /// The object graph to be serialized. - /// A serialized representation of the object graph provided. - public static byte[] Serialize(this ISerialize serializer, T value) where T : notnull - { -#if NET8_0_OR_GREATER - // Modern package targets use a pooled backing buffer to avoid MemoryStream's - // intermediate array allocation and growth copies on larger payloads. The public - // contract still returns an exact byte[] so consumers see the same API shape and - // ownership semantics as before; only temporary write storage is pooled. - var stream = new PooledWriteStream(); - try - { - serializer.Serialize(stream, value); - return stream.ToArray(); - } - finally - { - // Some serializers dispose the stream they are given before returning. Dispose - // is intentionally a no-op on PooledWriteStream so the extension can still copy - // the final bytes and then deterministically return the rented buffer here. - stream.ReturnBuffer(); - } -#else - using var stream = new MemoryStream(); - serializer.Serialize(stream, value); - return stream.ToArray(); -#endif - } - - /// - /// Deserializes the array of bytes provided. - /// - /// The type of object to be deserialized. - /// The serializer to use. - /// The serialized array of bytes. - /// The reconstituted object, if any. - public static T? Deserialize(this ISerialize serializer, byte[] serialized) - { - // add null or empty check - if (serialized == null || serialized.Length == 0) - { - throw new ArgumentNullException(nameof(serialized), "cannot be null or empty."); - } - - using var stream = new MemoryStream(serialized); - return serializer.Deserialize(stream); - } - -#if NET8_0_OR_GREATER - private sealed class PooledWriteStream : Stream - { - private const int InitialCapacity = 256; - private byte[] _buffer = ArrayPool.Shared.Rent(InitialCapacity); - private int _length; - private bool _bufferReturned; - - public override bool CanRead => false; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => _length; - - public override long Position - { - get => _length; - set => throw new NotSupportedException(); - } - - public override void Flush() - { - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - ArgumentNullException.ThrowIfNull(buffer); - if ((uint)offset > (uint)buffer.Length || (uint)count > (uint)(buffer.Length - offset)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - - Write(buffer.AsSpan(offset, count)); - } - - public override void Write(ReadOnlySpan buffer) - { - EnsureCapacity(_length + buffer.Length); - buffer.CopyTo(_buffer.AsSpan(_length)); - _length += buffer.Length; - } - - public override void WriteByte(byte value) - { - EnsureCapacity(_length + 1); - _buffer[_length] = value; - _length++; - } - - public byte[] ToArray() - { - var result = new byte[_length]; - _buffer.AsSpan(0, _length).CopyTo(result); - return result; - } - - public void ReturnBuffer() - { - if (_bufferReturned) - { - return; - } - - ArrayPool.Shared.Return(_buffer); - _bufferReturned = true; - } - -#pragma warning disable CA2215 // Dispose methods should call base class dispose - protected override void Dispose(bool disposing) -#pragma warning restore CA2215 // Dispose methods should call base class dispose - { - // Do not return the buffer from Dispose. Serializers commonly dispose the output - // stream they receive, but Serialize() still has to read back the bytes after - // the serializer returns. The extension method owns the pooled buffer lifetime. - } - - private void EnsureCapacity(int requiredCapacity) - { - if (requiredCapacity <= _buffer.Length) - { - return; - } - - int newCapacity = _buffer.Length; - while (newCapacity < requiredCapacity) - { - newCapacity *= 2; - } - - var newBuffer = ArrayPool.Shared.Rent(newCapacity); - _buffer.AsSpan(0, _length).CopyTo(newBuffer); - ArrayPool.Shared.Return(_buffer); - _buffer = newBuffer; - } - } -#endif - } -} diff --git a/src/NEventStore/SerializationWireup.cs b/src/NEventStore/SerializationWireup.cs deleted file mode 100644 index e98f8f591..000000000 --- a/src/NEventStore/SerializationWireup.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.Extensions.Logging; -using NEventStore.Logging; -using NEventStore.Serialization; - -namespace NEventStore -{ - /// - /// Represents the configuration for serialization. - /// - public class SerializationWireup : Wireup - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(SerializationWireup)); - - /// - /// Initializes a new instance of the SerializationWireup class. - /// - public SerializationWireup(Wireup inner, ISerialize serializer) - : base(inner) - { - Container.Register(serializer); - } - - /// - /// Enable GZip compression on the serialized stream. - /// - public SerializationWireup Compress() - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.ConfiguringCompression); - } - var wrapped = Container.Resolve() - ?? throw new InvalidOperationException("Serialize not configured."); - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Messages.WrappingSerializerGZip, wrapped.GetType()); - } - Container.Register(new GzipSerializer(wrapped)); - return this; - } - - /// - /// Enable Rijndael encryption on the serialized stream. - /// - public SerializationWireup EncryptWith(byte[] encryptionKey) - { - if (Logger.IsEnabled(LogLevel.Debug)) - { - Logger.LogDebug(Messages.ConfiguringEncryption); - } - var wrapped = Container.Resolve() - ?? throw new InvalidOperationException("Serialize not configured."); - - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Messages.WrappingSerializerEncryption, wrapped.GetType()); - } - Container.Register(new RijndaelSerializer(wrapped, encryptionKey)); - return this; - } - } -} \ No newline at end of file diff --git a/src/NEventStore/SerializationWireupExtensions.cs b/src/NEventStore/SerializationWireupExtensions.cs deleted file mode 100644 index faa39c577..000000000 --- a/src/NEventStore/SerializationWireupExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using NEventStore.Logging; -using Microsoft.Extensions.Logging; -using NEventStore.Serialization; - -namespace NEventStore -{ - /// - /// Represents the configuration for serialization. - /// - public static class SerializationWireupExtensions - { - private static readonly ILogger Logger = LogFactory.BuildLogger(typeof(PersistenceWireup)); - - /// - /// Configure custom serialization. - /// - public static SerializationWireup UsingCustomSerialization(this PersistenceWireup wireup, ISerialize serializer) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.WireupSetSerializer, serializer.GetType()); - } - return new SerializationWireup(wireup, serializer); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Snapshot.cs b/src/NEventStore/Snapshot.cs deleted file mode 100644 index 6fb768167..000000000 --- a/src/NEventStore/Snapshot.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Runtime.Serialization; - -namespace NEventStore -{ - /// - /// Represents a materialized view of a stream at specific revision. - /// - [DataContract] - [Serializable] - public class Snapshot : ISnapshot - { - /// - /// Initializes a new instance of the Snapshot class for the default bucket. - /// - /// The value which uniquely identifies the stream to which the snapshot applies. - /// The position at which the snapshot applies. - /// The snapshot or materialized view of the stream at the revision indicated. - public Snapshot(string streamId, int streamRevision, object payload) - : this(Bucket.Default, streamId, streamRevision, payload) - {} - - /// - /// Initializes a new instance of the Snapshot class. - /// - /// The value which uniquely identifies bucket the stream belongs to. - /// The value which uniquely identifies the stream to which the snapshot applies. - /// The position at which the snapshot applies. - /// The snapshot or materialized view of the stream at the revision indicated. - public Snapshot(string bucketId, string streamId, int streamRevision, object payload) - : this() - { - BucketId = bucketId; - StreamId = streamId; - StreamRevision = streamRevision; - Payload = payload; - } - - /// - /// Initializes a new instance of the Snapshot class. - /// - protected Snapshot() - {} - - /// - [DataMember] - public virtual string BucketId { get; private set; } - - /// - /// Gets the value which uniquely identifies the stream to which the snapshot applies. - /// - [DataMember] - public virtual string StreamId { get; private set; } - - /// - /// Gets the position at which the snapshot applies. - /// - [DataMember] - public virtual int StreamRevision { get; private set; } - - /// - /// Gets the snapshot or materialized view of the stream at the revision indicated. - /// - [DataMember] - public virtual object Payload { get; private set; } - } -} \ No newline at end of file diff --git a/src/NEventStore/StoreEventsExtensions.cs b/src/NEventStore/StoreEventsExtensions.cs deleted file mode 100644 index bbd8a8d5d..000000000 --- a/src/NEventStore/StoreEventsExtensions.cs +++ /dev/null @@ -1,167 +0,0 @@ -using NEventStore.Persistence; - -namespace NEventStore -{ - /// - /// Provides extension methods for . - /// - public static class StoreEventsExtensions - { - /// - /// Creates a new stream. - /// - /// The store events instance. - /// The value which uniquely identifies the stream to be created. - /// An empty stream. - public static IEventStream CreateStream(this IStoreEvents storeEvents, Guid streamId) - { - return CreateStream(storeEvents, Bucket.Default, streamId); - } - - /// - /// Creates a new stream. - /// - /// The store events instance. - /// The value which uniquely identifies the stream to be created. - /// An empty stream. - public static IEventStream CreateStream(this IStoreEvents storeEvents, string streamId) - { - EnsureStoreEventsNotNull(storeEvents); - return storeEvents.CreateStream(Bucket.Default, streamId); - } - - /// - /// Creates a new stream. - /// - /// The store events instance. - /// The value which uniquely identifies bucket the stream belongs to. - /// The value which uniquely identifies the stream within the bucket to be created. - /// An empty stream. - public static IEventStream CreateStream(this IStoreEvents storeEvents, string bucketId, Guid streamId) - { - EnsureStoreEventsNotNull(storeEvents); - return storeEvents.CreateStream(bucketId, streamId.ToString()); - } - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The store events instance. - /// The value which uniquely identifies the stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events represented as a stream. - /// - /// - /// - public static IEventStream OpenStream(this IStoreEvents storeEvents, Guid streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue) - { - EnsureStoreEventsNotNull(storeEvents); - return OpenStream(storeEvents, Bucket.Default, streamId, minRevision, maxRevision); - } - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The store events instance. - /// The value which uniquely identifies the stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events represented as a stream. - /// - /// - /// - public static IEventStream OpenStream(this IStoreEvents storeEvents, string streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue) - { - EnsureStoreEventsNotNull(storeEvents); - return storeEvents.OpenStream(Bucket.Default, streamId, minRevision, maxRevision); - } - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The store events instance. - /// The value which uniquely identifies bucket the stream belongs to. - /// The value which uniquely identifies the stream within the bucket to be created. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// A series of committed events represented as a stream. - /// - /// - /// - public static IEventStream OpenStream(this IStoreEvents storeEvents, string bucketId, Guid streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue) - { - EnsureStoreEventsNotNull(storeEvents); - return storeEvents.OpenStream(bucketId, streamId.ToString(), minRevision, maxRevision); - } - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The store events instance. - /// The value which uniquely identifies the stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// The token to monitor for cancellation requests. - /// A series of committed events represented as a stream. - /// - /// - /// - public static Task OpenStreamAsync(this IStoreEvents storeEvents, Guid streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue, CancellationToken cancellationToken = default) - { - EnsureStoreEventsNotNull(storeEvents); - return OpenStreamAsync(storeEvents, Bucket.Default, streamId, minRevision, maxRevision, cancellationToken); - } - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The store events instance. - /// The value which uniquely identifies the stream from which the events will be read. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// The token to monitor for cancellation requests. - /// A series of committed events represented as a stream. - /// - /// - /// - public static Task OpenStreamAsync(this IStoreEvents storeEvents, string streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue, CancellationToken cancellationToken = default) - { - EnsureStoreEventsNotNull(storeEvents); - return storeEvents.OpenStreamAsync(Bucket.Default, streamId, minRevision, maxRevision, cancellationToken); - } - - /// - /// Reads the stream indicated from the minimum revision specified up to the maximum revision specified or creates - /// an empty stream if no commits are found and a minimum revision of zero is provided. - /// - /// The store events instance. - /// The value which uniquely identifies bucket the stream belongs to. - /// The value which uniquely identifies the stream within the bucket to be created. - /// The minimum revision of the stream to be read. - /// The maximum revision of the stream to be read. - /// The token to monitor for cancellation requests. - /// A series of committed events represented as a stream. - /// - /// - /// - public static Task OpenStreamAsync(this IStoreEvents storeEvents, string bucketId, Guid streamId, int minRevision = int.MinValue, int maxRevision = int.MaxValue, CancellationToken cancellationToken = default) - { - EnsureStoreEventsNotNull(storeEvents); - return storeEvents.OpenStreamAsync(bucketId, streamId.ToString(), minRevision, maxRevision, cancellationToken); - } - - private static void EnsureStoreEventsNotNull(IStoreEvents storeEvents) - { - if (storeEvents == null) - { - throw new ArgumentNullException(nameof(storeEvents)); - } - } - } -} \ No newline at end of file diff --git a/src/NEventStore/StreamHeadObserver.cs b/src/NEventStore/StreamHeadObserver.cs deleted file mode 100644 index 1c30129cf..000000000 --- a/src/NEventStore/StreamHeadObserver.cs +++ /dev/null @@ -1,54 +0,0 @@ -using NEventStore.Persistence; -using System.Runtime.ExceptionServices; - -namespace NEventStore -{ - /// - /// Represents an async observer that can receive and stores commits from a stream. - /// Can be used as base class for other observers. - /// - public class StreamHeadObserver : IAsyncObserver - { - /// - /// The list of commits read from the stream - /// - public IList StreamHeads { get; } = []; - - /// - /// Indicates if the read operation has completed - /// - public bool ReadCompleted { get; private set; } - - /// - /// Store the commits received from the stream - /// - public virtual Task OnNextAsync(IStreamHead value, CancellationToken cancellationToken) - { - StreamHeads.Add(value); - return Task.FromResult(true); - } - - /// - /// Notifies the observer that the provider has experienced an error condition. - /// - /// Preserve the stack trace and rethrow the exception that occurred while reading commits from the stream. - /// - /// - /// Override this method to log and handle the error. - /// - /// - public virtual Task OnErrorAsync(Exception ex, CancellationToken cancellationToken) - { - // Preserve the stack trace and rethrow the exception - ExceptionDispatchInfo.Capture(ex).Throw(); - return Task.CompletedTask; - } - - /// - public virtual Task OnCompletedAsync(CancellationToken cancellationToken) - { - ReadCompleted = true; - return Task.CompletedTask; - } - } -} \ No newline at end of file diff --git a/src/NEventStore/StreamNotFoundException.cs b/src/NEventStore/StreamNotFoundException.cs deleted file mode 100644 index 214380fdd..000000000 --- a/src/NEventStore/StreamNotFoundException.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace NEventStore -{ - /// - /// Represents an attempt to retrieve a nonexistent event stream. - /// - public class StreamNotFoundException : Exception - { - /// - /// Initializes a new instance of the StreamNotFoundException class. - /// - public StreamNotFoundException() - {} - - /// - /// Initializes a new instance of the StreamNotFoundException class. - /// - /// The message that describes the error. - public StreamNotFoundException(string message) - : base(message) - {} - - /// - /// Initializes a new instance of the StreamNotFoundException class. - /// - /// The message that describes the error. - /// The message that is the cause of the current exception. - public StreamNotFoundException(string message, Exception innerException) - : base(message, innerException) - {} - } -} \ No newline at end of file diff --git a/src/NEventStore/SystemTime.cs b/src/NEventStore/SystemTime.cs deleted file mode 100644 index ad5cb4ae4..000000000 --- a/src/NEventStore/SystemTime.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NEventStore -{ - /// - /// Provides the ability to override the current moment in time to facilitate testing. - /// Original idea by Ayende Rahien: - /// http://ayende.com/Blog/archive/2008/07/07/Dealing-with-time-in-tests.aspx - /// - public static class SystemTime - { - /// - /// The callback to be used to resolve the current moment in time. - /// - public static Func? Resolver { get; set; } - - /// - /// Gets the current moment in time. - /// - public static DateTime UtcNow - { - get { return Resolver == null ? DateTime.UtcNow : Resolver(); } - } - } -} \ No newline at end of file diff --git a/src/NEventStore/TaskHelpers.cs b/src/NEventStore/TaskHelpers.cs deleted file mode 100644 index 173ead8e4..000000000 --- a/src/NEventStore/TaskHelpers.cs +++ /dev/null @@ -1,78 +0,0 @@ -namespace NEventStore -{ - internal static class TaskHelpers - { - internal static Task Delay(double milliseconds, CancellationToken cancellationToken) - { - var tcs = new TaskCompletionSource(); - TimerCallback callback = (_) => tcs.TrySetResult(true); -#pragma warning disable IDE0067 // Dispose objects before losing scope - var timer = new Timer(callback, null, 0, Convert.ToInt32(milliseconds)); -#pragma warning restore IDE0067 // Dispose objects before losing scope - CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.Register(() => - { - timer.Dispose(); - tcs.TrySetCanceled(); - }); - return tcs.Task.ContinueWith(_ => - { - cancellationTokenRegistration.Dispose(); - timer.Dispose(); - }, TaskContinuationOptions.ExecuteSynchronously); - } - - public static void WhenCompleted(this Task task, Action> onComplete, Action> onFaulted, bool execSync = false) - { - if (task.IsCompleted) - { - if (task.IsFaulted) - { - onFaulted.Invoke(task); - return; - } - - onComplete.Invoke(task); - return; - } - - task.ContinueWith( - onComplete, - execSync ? - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion : - TaskContinuationOptions.OnlyOnRanToCompletion); - - task.ContinueWith( - onFaulted, - execSync ? - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted : - TaskContinuationOptions.OnlyOnFaulted); - } - - public static void WhenCompleted(this Task task, Action onComplete, Action onFaulted, bool execSync = false) - { - if (task.IsCompleted) - { - if (task.IsFaulted) - { - onFaulted.Invoke(task); - return; - } - - onComplete.Invoke(task); - return; - } - - task.ContinueWith( - onComplete, - execSync ? - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion : - TaskContinuationOptions.OnlyOnRanToCompletion); - - task.ContinueWith( - onFaulted, - execSync ? - TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted : - TaskContinuationOptions.OnlyOnFaulted); - } - } -} \ No newline at end of file diff --git a/src/NEventStore/Wireup.cs b/src/NEventStore/Wireup.cs deleted file mode 100644 index 5a6cbdd00..000000000 --- a/src/NEventStore/Wireup.cs +++ /dev/null @@ -1,157 +0,0 @@ -using NEventStore.Conversion; -using NEventStore.Persistence; -using NEventStore.Persistence.InMemory; -using NEventStore.Logging; -using Microsoft.Extensions.Logging; - -namespace NEventStore -{ - /// - /// Represents the configuration for the event store. - /// - public class Wireup - { - private readonly NanoContainer? _container; - private readonly Wireup? _inner; - private readonly ILogger Logger = LogFactory.BuildLogger(typeof(Wireup)); - - /// - /// Initializes a new instance of the Wireup class. - /// - protected Wireup(NanoContainer container) - { - _container = container ?? throw new ArgumentNullException(nameof(container)); - } - - /// - /// Initializes a new instance of the Wireup class. - /// - protected Wireup(Wireup inner) - { - _inner = inner ?? throw new ArgumentNullException(nameof(inner)); - } - - /// - /// Gets the container. - /// - protected NanoContainer Container - { - get { return _container ?? _inner!.Container; } - } - - /// - /// Initializes a new instance of the Wire-up class. - /// - public static Wireup Init() - { - var container = new NanoContainer(); - - container.Register(new InMemoryPersistenceEngine()); - container.Register(BuildEventStore); - - return new Wireup(container); - } - - /// - /// Registers a service with the container. - /// - public virtual Wireup Register(T instance) where T : class - { - Container.Register(instance); - return this; - } - - /// - /// Add pipeline hooks to the processing pipeline. - /// - public virtual Wireup HookIntoPipelineUsing(IEnumerable hooks) - { - return HookIntoPipelineUsing((hooks ?? []).ToArray()); - } - - /// - /// Add pipeline hooks to the processing pipeline. - /// - public virtual Wireup HookIntoPipelineUsing(params IPipelineHook[] hooks) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.WireupHookIntoPipeline, string.Join(", ", hooks.Select(h => h.GetType()))); - } - ICollection collection = (hooks ?? []).Where(x => x != null).ToArray(); - Container.Register(collection); - return this; - } - - /// - /// Add pipeline hooks to the processing pipeline. - /// - public virtual Wireup HookIntoPipelineUsing(IEnumerable hooks) - { - return HookIntoPipelineUsing((hooks ?? []).ToArray()); - } - - /// - /// Add pipeline hooks to the processing pipeline. - /// Asynchronous hooks are executed after the synchronous hooks. - /// - public virtual Wireup HookIntoPipelineUsing(params IPipelineHookAsync[] hooks) - { - if (Logger.IsEnabled(LogLevel.Information)) - { - Logger.LogInformation(Resources.WireupHookIntoPipeline, string.Join(", ", hooks.Select(h => h.GetType()))); - } - ICollection collection = (hooks ?? []).Where(x => x != null).ToArray(); - Container.Register(collection); - return this; - } - - /// - /// Builds the configured event store. - /// - public virtual IStoreEvents Build() - { - if (_inner != null) - { - return _inner.Build(); - } - - return Container.Resolve() - ?? throw new InvalidOperationException("IStoreEvents was not registered."); - } - - /// - /// - /// Provide some additional concurrency checks to avoid useless roundtrips to the databases in a non-transactional environment. - /// - /// - /// If you enable any sort of two-phase commit and/or transactional behavior on the Persistence drivers - /// you should not use the module. - /// - /// - public Wireup UseOptimisticPipelineHook(int maxStreamsToTrack = OptimisticPipelineHook.MaxStreamsToTrack) - { - Container.Register(_ => new OptimisticPipelineHook(maxStreamsToTrack)); - return this; - } - - private static IStoreEvents BuildEventStore(NanoContainer context) - { - var concurrency = context.Resolve(); - var upconverter = context.Resolve(); - - ICollection hooks = context.Resolve>() ?? []; - var pipelineHooks = new List(hooks); - if (concurrency != null) - { - pipelineHooks.Add(concurrency); - } - if (upconverter != null) - { - pipelineHooks.Add(upconverter); - } - ICollection hooksAsync = context.Resolve>() ?? []; - return new OptimisticEventStore(context.Resolve()!, pipelineHooks, hooksAsync); - } - } -} \ No newline at end of file diff --git a/src/tests_dotnetcore.runsettings b/src/tests_dotnetcore.runsettings deleted file mode 100644 index a14d14a9a..000000000 --- a/src/tests_dotnetcore.runsettings +++ /dev/null @@ -1,6 +0,0 @@ - - - - true - - \ No newline at end of file diff --git a/stylesheets/pygment_trac.css b/stylesheets/pygment_trac.css new file mode 100644 index 000000000..e65cedff6 --- /dev/null +++ b/stylesheets/pygment_trac.css @@ -0,0 +1,70 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f0f3f3; } +.highlight .c { color: #0099FF; font-style: italic } /* Comment */ +.highlight .err { color: #AA0000; background-color: #FFAAAA } /* Error */ +.highlight .k { color: #006699; font-weight: bold } /* Keyword */ +.highlight .o { color: #555555 } /* Operator */ +.highlight .cm { color: #0099FF; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #009999 } /* Comment.Preproc */ +.highlight .c1 { color: #0099FF; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #0099FF; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #003300; font-weight: bold } /* Generic.Heading */ +.highlight .gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */ +.highlight .go { color: #AAAAAA } /* Generic.Output */ +.highlight .gp { color: #000099; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #003300; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #99CC66 } /* Generic.Traceback */ +.highlight .kc { color: #006699; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #006699; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #006699; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #006699 } /* Keyword.Pseudo */ +.highlight .kr { color: #006699; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #007788; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #FF6600 } /* Literal.Number */ +.highlight .s { color: #CC3300 } /* Literal.String */ +.highlight .na { color: #330099 } /* Name.Attribute */ +.highlight .nb { color: #336666 } /* Name.Builtin */ +.highlight .nc { color: #00AA88; font-weight: bold } /* Name.Class */ +.highlight .no { color: #336600 } /* Name.Constant */ +.highlight .nd { color: #9999FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CC0000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #CC00FF } /* Name.Function */ +.highlight .nl { color: #9999FF } /* Name.Label */ +.highlight .nn { color: #00CCFF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #330099; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #003333 } /* Name.Variable */ +.highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #FF6600 } /* Literal.Number.Float */ +.highlight .mh { color: #FF6600 } /* Literal.Number.Hex */ +.highlight .mi { color: #FF6600 } /* Literal.Number.Integer */ +.highlight .mo { color: #FF6600 } /* Literal.Number.Oct */ +.highlight .sb { color: #CC3300 } /* Literal.String.Backtick */ +.highlight .sc { color: #CC3300 } /* Literal.String.Char */ +.highlight .sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #CC3300 } /* Literal.String.Double */ +.highlight .se { color: #CC3300; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #CC3300 } /* Literal.String.Heredoc */ +.highlight .si { color: #AA0000 } /* Literal.String.Interpol */ +.highlight .sx { color: #CC3300 } /* Literal.String.Other */ +.highlight .sr { color: #33AAAA } /* Literal.String.Regex */ +.highlight .s1 { color: #CC3300 } /* Literal.String.Single */ +.highlight .ss { color: #FFCC33 } /* Literal.String.Symbol */ +.highlight .bp { color: #336666 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #003333 } /* Name.Variable.Class */ +.highlight .vg { color: #003333 } /* Name.Variable.Global */ +.highlight .vi { color: #003333 } /* Name.Variable.Instance */ +.highlight .il { color: #FF6600 } /* Literal.Number.Integer.Long */ + +.type-csharp .highlight .k { color: #0000FF } +.type-csharp .highlight .kt { color: #0000FF } +.type-csharp .highlight .nf { color: #000000; font-weight: normal } +.type-csharp .highlight .nc { color: #2B91AF } +.type-csharp .highlight .nn { color: #000000 } +.type-csharp .highlight .s { color: #A31515 } +.type-csharp .highlight .sc { color: #A31515 } diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css new file mode 100644 index 000000000..d067c80d2 --- /dev/null +++ b/stylesheets/stylesheet.css @@ -0,0 +1,428 @@ +/******************************************************************************* +Slate Theme for GitHub Pages +by Jason Costello, @jsncostello +*******************************************************************************/ + +@import url(pygment_trac.css); + +/******************************************************************************* +MeyerWeb Reset +*******************************************************************************/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +ol, ul { + list-style: none; +} + +blockquote, q { +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +a:focus { + outline: none; +} + +/******************************************************************************* +Theme Styles +*******************************************************************************/ + +body { + box-sizing: border-box; + color:#373737; + background: #212121; + font-size: 16px; + font-family: 'Myriad Pro', Calibri, Helvetica, Arial, sans-serif; + line-height: 1.5; + -webkit-font-smoothing: antialiased; +} + +h1, h2, h3, h4, h5, h6 { + margin: 10px 0; + font-weight: 700; + color:#222222; + font-family: 'Lucida Grande', 'Calibri', Helvetica, Arial, sans-serif; + letter-spacing: -1px; +} + +h1 { + font-size: 36px; + font-weight: 700; +} + +h2 { + padding-bottom: 10px; + font-size: 32px; + background: url('../images/bg_hr.png') repeat-x bottom; +} + +h3 { + font-size: 24px; +} + +h4 { + font-size: 21px; +} + +h5 { + font-size: 18px; +} + +h6 { + font-size: 16px; +} + +p { + margin: 10px 0 15px 0; +} + +footer p { + color: #f2f2f2; +} + +a { + text-decoration: none; + color: #007edf; + text-shadow: none; + + transition: color 0.5s ease; + transition: text-shadow 0.5s ease; + -webkit-transition: color 0.5s ease; + -webkit-transition: text-shadow 0.5s ease; + -moz-transition: color 0.5s ease; + -moz-transition: text-shadow 0.5s ease; + -o-transition: color 0.5s ease; + -o-transition: text-shadow 0.5s ease; + -ms-transition: color 0.5s ease; + -ms-transition: text-shadow 0.5s ease; +} + +#main_content a:hover { + color: #0069ba; + text-shadow: #0090ff 0px 0px 2px; +} + +footer a:hover { + color: #43adff; + text-shadow: #0090ff 0px 0px 2px; +} + +em { + font-style: italic; +} + +strong { + font-weight: bold; +} + +img { + position: relative; + margin: 0 auto; + max-width: 739px; + padding: 5px; + margin: 10px 0 10px 0; + border: 1px solid #ebebeb; + + box-shadow: 0 0 5px #ebebeb; + -webkit-box-shadow: 0 0 5px #ebebeb; + -moz-box-shadow: 0 0 5px #ebebeb; + -o-box-shadow: 0 0 5px #ebebeb; + -ms-box-shadow: 0 0 5px #ebebeb; +} + +pre, code { + width: 100%; + color: #222; + background-color: #fff; + + font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; + font-size: 14px; + + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + + + +} + +pre { + width: 100%; + padding: 10px; + box-shadow: 0 0 10px rgba(0,0,0,.1); + overflow: auto; +} + +code { + padding: 3px; + margin: 0 3px; + box-shadow: 0 0 10px rgba(0,0,0,.1); +} + +pre code { + display: block; + box-shadow: none; +} + +blockquote { + color: #666; + margin-bottom: 20px; + padding: 0 0 0 20px; + border-left: 3px solid #bbb; +} + +ul, ol, dl { + margin-bottom: 15px +} + +ul li { + list-style: inside; + padding-left: 20px; +} + +ol li { + list-style: decimal inside; + padding-left: 20px; +} + +dl dt { + font-weight: bold; +} + +dl dd { + padding-left: 20px; + font-style: italic; +} + +dl p { + padding-left: 20px; + font-style: italic; +} + +hr { + height: 1px; + margin-bottom: 5px; + border: none; + background: url('../images/bg_hr.png') repeat-x center; +} + +table { + border: 1px solid #373737; + margin-bottom: 20px; + text-align: left; + } + +th { + font-family: 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; + padding: 10px; + background: #373737; + color: #fff; + } + +td { + padding: 10px; + border: 1px solid #373737; + } + +form { + background: #f2f2f2; + padding: 20px; +} + + + +/******************************************************************************* +Full-Width Styles +*******************************************************************************/ + +.outer { + width: 100%; +} + +.inner { + position: relative; + max-width: 640px; + padding: 20px 10px; + margin: 0 auto; +} + +#forkme_banner { + display: block; + position: absolute; + top:0; + right: 10px; + z-index: 10; + padding: 10px 50px 10px 10px; + color: #fff; + background: url('../images/blacktocat.png') #0090ff no-repeat 95% 50%; + font-weight: 700; + box-shadow: 0 0 10px rgba(0,0,0,.5); + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +#header_wrap { + background: #212121; + background: -moz-linear-gradient(top, #373737, #212121); + background: -webkit-linear-gradient(top, #373737, #212121); + background: -ms-linear-gradient(top, #373737, #212121); + background: -o-linear-gradient(top, #373737, #212121); + background: linear-gradient(top, #373737, #212121); +} + +#header_wrap .inner { + padding: 50px 10px 30px 10px; +} + +#project_title { + margin: 0; + color: #fff; + font-size: 42px; + font-weight: 700; + text-shadow: #111 0px 0px 10px; +} + +#project_tagline { + color: #fff; + font-size: 24px; + font-weight: 300; + background: none; + text-shadow: #111 0px 0px 10px; +} + +#downloads { + position: absolute; + width: 210px; + z-index: 10; + bottom: -40px; + right: 0; + height: 70px; + background: url('../images/icon_download.png') no-repeat 0% 90%; +} + +.zip_download_link { + display: block; + float: right; + width: 90px; + height:70px; + text-indent: -5000px; + overflow: hidden; + background: url(../images/sprite_download.png) no-repeat bottom left; +} + +.tar_download_link { + display: block; + float: right; + width: 90px; + height:70px; + text-indent: -5000px; + overflow: hidden; + background: url(../images/sprite_download.png) no-repeat bottom right; + margin-left: 10px; +} + +.zip_download_link:hover { + background: url(../images/sprite_download.png) no-repeat top left; +} + +.tar_download_link:hover { + background: url(../images/sprite_download.png) no-repeat top right; +} + +#main_content_wrap { + background: #f2f2f2; + border-top: 1px solid #111; + border-bottom: 1px solid #111; +} + +#main_content { + padding-top: 40px; +} + +#footer_wrap { + background: #212121; +} + + + +/******************************************************************************* +Small Device Styles +*******************************************************************************/ + +@media screen and (max-width: 480px) { + body { + font-size:14px; + } + + #downloads { + display: none; + } + + .inner { + min-width: 320px; + max-width: 480px; + } + + #project_title { + font-size: 32px; + } + + h1 { + font-size: 28px; + } + + h2 { + font-size: 24px; + } + + h3 { + font-size: 21px; + } + + h4 { + font-size: 18px; + } + + h5 { + font-size: 14px; + } + + h6 { + font-size: 12px; + } + + code, pre { + min-width: 320px; + max-width: 480px; + font-size: 11px; + } + +}