Skip to content

fix(background-jobs): normalize DateTime via EF Core ValueConverter and simplify job store API#56

Merged
yilmaztayfun merged 3 commits into
masterfrom
55-ef-core-datetime-valueconverter-for-iclock-normalization-daprjobexecutionbridge-status-aware-lookup
Apr 12, 2026
Merged

fix(background-jobs): normalize DateTime via EF Core ValueConverter and simplify job store API#56
yilmaztayfun merged 3 commits into
masterfrom
55-ef-core-datetime-valueconverter-for-iclock-normalization-daprjobexecutionbridge-status-aware-lookup

Conversation

@yilmaztayfun
Copy link
Copy Markdown
Contributor

@yilmaztayfun yilmaztayfun commented Apr 12, 2026

Summary

  • EF Core DateTime normalization: Added ClockDateTimeValueConverter and ClockDateTimeOffsetValueConverter backed by IClock.NormalizeToUtc, applied automatically to all DateTime/DateTimeOffset properties at model-build time via ConfigureByConvention.
  • Job store API simplification: Removed the overloaded GetByJobNameAsync(name, status) and consolidated active-job filtering (Scheduled | Running) into the single GetByJobNameAsync(name) method. Introduced IsActive computed property on BackgroundJobInfo.
  • Upsert by job name: SaveOrUpdateAsync now looks up existing jobs by JobName instead of Id, enabling correct upsert-by-name semantics.
  • Idempotency improvements: Added Failed status to the idempotency guard in JobDispatcher so previously-failed jobs are not reprocessed.
  • Dapr overwrite: Pass overwrite: true when scheduling Dapr jobs to allow rescheduling without conflicts.
  • UoW aspect: Commented out IApplicationService marker in UnitOfWorkAspectProvider to prevent automatic UoW wrapping on all application services.

Test plan

  • Verify EF Core reads DateTime/DateTimeOffset columns with correct UTC kind after normalization
  • Confirm that scheduling the same job twice upserts rather than duplicates
  • Confirm that Failed jobs are skipped on re-dispatch
  • Confirm that Dapr job rescheduling works without conflicts (overwrite: true)

Made with Cursor

Summary by Sourcery

Normalize DateTime handling via clock-based EF Core converters and refine background job processing semantics, including upsert-by-name behavior, active-job filtering, and more robust dispatch and scheduling.

New Features:

  • Introduce clock-backed EF Core value converters for DateTime and DateTimeOffset to ensure consistent UTC normalization across entities.
  • Add an IsActive convenience property on BackgroundJobInfo to represent scheduled or running jobs.

Bug Fixes:

  • Ensure SaveAsync upserts background jobs by JobName instead of Id to avoid duplicate jobs for the same scheduler identifier.
  • Treat Failed background jobs as already processed to prevent unintended re-dispatch.
  • Filter GetByJobNameAsync to return only active jobs so dispatchers and schedulers do not operate on completed or cancelled entries.
  • Allow Dapr job rescheduling without conflicts by enabling overwrite when scheduling jobs.
  • Prevent failures when writing domain events to the outbox from aborting dispatch of remaining events by catching and logging publishing errors.

Enhancements:

  • Propagate IClock into AetherDbContext model configuration so DateTime normalization is applied by convention during model building.
  • Relax unit-of-work auto-wrapping by excluding IApplicationService as a marker interface for UoW aspects.
  • Clarify Dapr job execution logging when a job cannot be found as scheduled, indicating it may already be completed, failed, or cancelled.
  • Standardize DateTime column configuration with value converters applied conditionally based on clock availability.
  • Tidy comments and XML documentation on job store APIs to reflect active job semantics.

Summary by CodeRabbit

  • New Features

    • Added IsActive property to background job information.
    • Implemented automatic UTC date/time normalization for database entities.
  • Bug Fixes

    • Failed background jobs are now treated as already processed to prevent reprocessing.
    • Dapr job scheduling now properly overwrites existing jobs with the same name.
    • Event dispatching with outbox now handles errors gracefully without propagating exceptions.
    • Missing background jobs now log as warnings instead of errors.
  • Documentation

    • Clarified background job store behavior for retrieving active jobs.

…rter and guard job re-dispatch by status

Scope 1 — EF Core DateTime read normalization (closes #55 scope 1):
- Add ClockDateTimeValueConverter and ClockDateTimeOffsetValueConverter,
  both backed by IClock.NormalizeToUtc, covering DateTime, DateTime?,
  DateTimeOffset, and DateTimeOffset? via EF Core null-lifting.
- Inject optional IClock into AetherDbContext<TDbContext> primary constructor
  so the model-building pipeline can access the clock without breaking
  existing derived DbContextes.
- Thread IClock through ConfigureByConvention → TryConfigureDateTimeUtc and
  apply the appropriate converter to every DateTime/DateTimeOffset property
  at model build time, ensuring consistent UTC Kind/Offset on both read and
  write paths for all entities.

Scope 2 — DaprJobExecutionBridge status-aware dispatch guard (closes #55 scope 2):
- Add IJobStore.GetByJobNameAsync(string, BackgroundJobStatus?, CancellationToken)
  overload; nulls preserves existing no-filter semantics.
- Implement the overload in EfCoreJobStore with a conditional Where clause.
- Update DaprJobExecutionBridge to query only Scheduled jobs; log a warning
  and return early when the job is not found in the expected state, preventing
  re-dispatch of already completed, failed, or cancelled jobs.
…ency

- Remove overloaded  and consolidate
  active-job filtering (Scheduled | Running) into the single
   method; introduce  computed
  property on
- Change  to look up existing jobs by
  instead of uid=502(U0B006) gid=20(staff) groups=20(staff),501(awagent),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),701(com.apple.sharepoint.group.1),702(com.apple.sharepoint.group.2),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae), enabling upsert-by-name semantics
- Add  status to idempotency check in  so
  previously-failed jobs are not reprocessed
- Pass  when scheduling Dapr jobs to allow
  rescheduling without conflicts
- Comment out  marker in
  to prevent automatic UoW wrapping on all application services
@yilmaztayfun yilmaztayfun requested review from a team April 12, 2026 20:36
@yilmaztayfun yilmaztayfun self-assigned this Apr 12, 2026
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 12, 2026

Reviewer's Guide

Normalizes all EF Core DateTime/DateTimeOffset properties via clock-based value converters, adjusts the DbContext/model configuration to accept an IClock, simplifies and makes background job storage/idempotency semantics more robust (upsert-by-name, active-only lookups, skipping failed jobs), relaxes automatic UoW aspects, and hardens domain event and Dapr job handling/logging.

Sequence diagram for job persistence upsert-by-name and Dapr scheduling overwrite

sequenceDiagram
    actor Client
    participant ApplicationService
    participant IJobStore
    participant EfCoreJobStore
    participant AetherDbContext as DbContext
    participant Database
    participant DaprJobScheduler
    participant DaprJobsClient

    Client->>ApplicationService: Request to schedule job(jobName, handlerName, payload)
    ApplicationService->>IJobStore: SaveAsync(jobInfo)
    IJobStore->>EfCoreJobStore: SaveAsync(jobInfo)
    EfCoreJobStore->>EfCoreJobStore: GetByJobNameAsync(jobInfo.JobName)
    EfCoreJobStore->>DbContext: Query BackgroundJobs where JobName==jobInfo.JobName and Status in {Scheduled,Running}
    DbContext->>Database: SELECT ...
    Database-->>DbContext: Existing active job or null
    DbContext-->>EfCoreJobStore: Materialized BackgroundJobInfo

    alt Existing active job found
        EfCoreJobStore->>EfCoreJobStore: Update existing job, set ModifiedAt=UtcNow
        EfCoreJobStore->>DbContext: Apply current values
    else No active job found
        EfCoreJobStore->>EfCoreJobStore: Initialize new job, set Status=Scheduled
        EfCoreJobStore->>DbContext: Add job entity
    end

    DbContext->>Database: SaveChanges (insert or update)
    Database-->>DbContext: Acknowledged
    DbContext-->>EfCoreJobStore: Save completed
    EfCoreJobStore-->>IJobStore: SaveAsync completed
    IJobStore-->>ApplicationService: Return

    ApplicationService->>DaprJobScheduler: ScheduleJobAsync(jobName, schedule, payload)
    DaprJobScheduler->>DaprJobsClient: ScheduleJobAsync(jobName, schedule, payload, overwrite=true)
    DaprJobsClient-->>DaprJobScheduler: Scheduled or rescheduled
    DaprJobScheduler-->>ApplicationService: Return
    ApplicationService-->>Client: Job scheduled (upserted by name and Dapr job overwritten if existed)
Loading

Sequence diagram for domain event dispatch with outbox error handling

sequenceDiagram
    participant DbContext
    participant DomainEventDispatcher
    participant EventBus
    participant OutboxStore
    participant Logger

    DbContext->>DomainEventDispatcher: DispatchEventsAsync(eventEnvelopes)
    loop For each eventEnvelope
        DomainEventDispatcher->>DomainEventDispatcher: Extract metadata and event
        alt useOutboxDirectly is true
            DomainEventDispatcher->>Logger: LogDebug Writing domain event to outbox
            DomainEventDispatcher->>EventBus: PublishAsync(event, metadata, subject, useOutbox=true)
            EventBus->>OutboxStore: Persist outbox message within transaction
            OutboxStore-->>EventBus: Persisted
            EventBus-->>DomainEventDispatcher: Publish completed
            DomainEventDispatcher->>Logger: LogDebug Successfully wrote domain event to outbox
        else useOutboxDirectly is false
            DomainEventDispatcher->>EventBus: PublishAsync(event, metadata, subject, useOutbox=false)
            EventBus-->>DomainEventDispatcher: Publish completed
        end
    end

    par On PublishAsync exception when useOutboxDirectly is true
        EventBus-->>DomainEventDispatcher: throws Exception
        DomainEventDispatcher->>Logger: LogError Failed to publish domain event to outbox, continue with remaining events
    end

    DomainEventDispatcher-->>DbContext: DispatchEventsAsync completed (remaining events processed despite failures)
Loading

Class diagram for background job store and idempotency changes

classDiagram
    class BackgroundJobStatus {
        <<enum>>
        Scheduled
        Running
        Completed
        Failed
        Cancelled
    }

    class BackgroundJobInfo {
        +Guid Id
        +string HandlerName
        +string JobName
        +BackgroundJobStatus Status
        +bool IsActive
        +DateTime? CreationTime
        +DateTime? ModifiedAt
        +DateTime? HandledTime
        +byte[] Payload
        +string ExtraProperties
        +BackgroundJobInfo(Guid id, string handlerName, string jobName)
    }

    class IJobStore {
        <<interface>>
        +Task SaveAsync(BackgroundJobInfo jobInfo, CancellationToken cancellationToken)
        +Task<BackgroundJobInfo> GetAsync(Guid id, CancellationToken cancellationToken)
        +Task<BackgroundJobInfo> GetByJobNameAsync(string jobName, CancellationToken cancellationToken)
        +Task<IEnumerable<BackgroundJobInfo>> GetByHandlerNameAsync(string handlerName, CancellationToken cancellationToken)
        +Task UpdateStatusAsync(Guid id, BackgroundJobStatus status, DateTime? handledTime, CancellationToken cancellationToken)
    }

    class EfCoreJobStore {
        -AetherDbContext dbContext
        +EfCoreJobStore(AetherDbContext dbContext)
        +Task SaveAsync(BackgroundJobInfo jobInfo, CancellationToken cancellationToken)
        +Task<BackgroundJobInfo> GetAsync(Guid id, CancellationToken cancellationToken)
        +Task<BackgroundJobInfo> GetByJobNameAsync(string jobName, CancellationToken cancellationToken)
        +Task<IEnumerable<BackgroundJobInfo>> GetByHandlerNameAsync(string handlerName, CancellationToken cancellationToken)
        +Task UpdateStatusAsync(Guid id, BackgroundJobStatus status, DateTime? handledTime, CancellationToken cancellationToken)
    }

    class JobDispatcher {
        -ILogger logger
        +Task DispatchAsync(Guid jobId, string handlerName, CancellationToken cancellationToken)
        -bool IsJobAlreadyProcessed(BackgroundJobInfo jobInfo, Guid jobId, string handlerName)
    }

    class DaprJobExecutionBridge {
        -ILogger logger
        -IJobStore jobStore
        +Task ExecuteAsync(string jobName, ReadOnlyMemory~byte~ payload, CancellationToken cancellationToken)
    }

    IJobStore <|.. EfCoreJobStore
    BackgroundJobInfo --> BackgroundJobStatus
    DaprJobExecutionBridge ..> IJobStore : uses
    JobDispatcher ..> IJobStore : uses

    note for BackgroundJobInfo "IsActive returns true when Status is Scheduled or Running"
    note for EfCoreJobStore "SaveAsync performs upsert by JobName via GetByJobNameAsync filtering active jobs"
    note for JobDispatcher "IsJobAlreadyProcessed returns true for Completed, Cancelled, or Failed statuses"
Loading

Class diagram for EF Core clock-based DateTime normalization

classDiagram
    class IClock {
        <<interface>>
        +DateTime NormalizeToUtc(DateTime value)
        +DateTimeOffset NormalizeToUtc(DateTimeOffset value)
    }

    class ClockDateTimeValueConverter {
        +ClockDateTimeValueConverter(IClock clock)
    }

    class ClockDateTimeOffsetValueConverter {
        +ClockDateTimeOffsetValueConverter(IClock clock)
    }

    class EntityTypeBuilder {
        +IMutableEntityType Metadata
        +PropertyBuilder Property(string name)
    }

    class AetherEntityTypeBuilderExtensions {
        <<static>>
        +void ConfigureByConvention(EntityTypeBuilder b, IClock clock)
        +void TryConfigureDateTimeUtc(EntityTypeBuilder b, IClock clock)
    }

    class AetherDbContext~TDbContext~ {
        -IClock clock
        -IDomainEventSink eventSink
        +AetherDbContext(DbContextOptions~TDbContext~ options, IDomainEventSink eventSink, IClock clock)
        +Task<int> SaveChangesAsync(CancellationToken cancellationToken)
        +void OnModelCreating(ModelBuilder modelBuilder)
        +void ConfigureBaseProperties~TEntity~(ModelBuilder modelBuilder)
    }

    IClock <.. ClockDateTimeValueConverter : uses
    IClock <.. ClockDateTimeOffsetValueConverter : uses
    EntityTypeBuilder <.. AetherEntityTypeBuilderExtensions : extends
    IClock <.. AetherEntityTypeBuilderExtensions : uses
    AetherDbContext~TDbContext~ o--> IClock : holds
    AetherDbContext~TDbContext~ ..> AetherEntityTypeBuilderExtensions : calls ConfigureByConvention

    note for AetherEntityTypeBuilderExtensions "ConfigureByConvention applies TryConfigureDateTimeUtc and other conventions using the injected IClock"
    note for ClockDateTimeValueConverter "Normalizes DateTime via IClock.NormalizeToUtc on read and write"
    note for ClockDateTimeOffsetValueConverter "Normalizes DateTimeOffset via IClock.NormalizeToUtc on read and write"
    note for AetherDbContext~TDbContext~ "ConfigureBaseProperties calls ConfigureByConvention(clock) so all DateTime and DateTimeOffset properties use UTC normalization"
Loading

File-Level Changes

Change Details Files
Normalize DateTime/DateTimeOffset properties via clock-based EF Core value converters and propagate clock into model configuration.
  • Extend ConfigureByConvention/TryConfigureDateTimeUtc to accept an optional IClock and, when present, attach ClockDateTime/ClockDateTimeOffset value converters to all DateTime/DateTimeOffset properties while still forcing 'timestamp with time zone' column type.
  • Update AetherDbContext base constructor to optionally receive IClock and pass it to ConfigureByConvention during model configuration.
  • Introduce ClockDateTimeValueConverter and ClockDateTimeOffsetValueConverter classes that normalize values through IClock.NormalizeToUtc on both read and write paths.
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Domain/EntityFrameworkCore/Modeling/AetherEntityTypeBuilderExtensions.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Domain/EntityFrameworkCore/AetherDbContext.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Domain/EntityFrameworkCore/ValueComparers/ClockDateTimeValueConverter.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Domain/EntityFrameworkCore/ValueComparers/ClockDateTimeOffsetValueConverter.cs
Change background job persistence to upsert and query by job name with an active-job filter and expose an IsActive helper on BackgroundJobInfo.
  • Modify EfCoreJobStore.SaveAsync to look up existing jobs by JobName via GetByJobNameAsync instead of by Id to implement upsert-by-name semantics.
  • Change EfCoreJobStore.GetByJobNameAsync to filter to active jobs only (Scheduled or Running).
  • Add an IsActive computed property on BackgroundJobInfo that returns true when Status is Scheduled or Running.
  • Tidy ModifiedAt assignments in EfCoreJobStore to a consistent formatting style.
framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs
framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Entities/BackgroundJobInfo.cs
framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Repositories/IJobStore.cs
Strengthen job idempotency and Dapr scheduling semantics and improve behavior when a scheduled job is missing.
  • Extend JobDispatcher.IsJobAlreadyProcessed to treat Failed status as already-processed, logging and skipping re-dispatch for failed jobs.
  • Update DaprJobScheduler to pass overwrite: true to ScheduleJobAsync to allow rescheduling without conflicts for the same job name.
  • Adjust DaprJobExecutionBridge logging when a job is not found so it logs a warning that the job may already be completed/failed/cancelled instead of an error.
framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/JobDispatcher.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/Dapr/DaprJobScheduler.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/Dapr/DaprJobExecutionBridge.cs
Relax automatic unit-of-work aspect registration for application services.
  • Comment out the IApplicationService marker interface in UnitOfWorkAspectProvider.MarkerInterfaceNames so application services are no longer implicitly wrapped in unit-of-work aspects.
framework/src/BBT.Aether.Aspects/BBT/Aether/Aspects/Uow/UnitOfWorkAspectProvider.cs
Harden domain event dispatch to outbox by handling publish failures gracefully.
  • Wrap the outbox PublishAsync call in DomainEventDispatcher in a try/catch, logging errors when publishing fails but continuing to process remaining events; keep debug logging on successful publish paths.
framework/src/BBT.Aether.Infrastructure/BBT/Aether/Domain/EntityFrameworkCore/DomainEventDispatcher.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#55 Normalize all DateTime and DateTimeOffset properties via an EF Core ValueConverter backed by IClock.NormalizeToUtc on both read and write paths, applied globally (including nullable types) through model configuration.
#55 Make Dapr job execution status-aware so that jobs in Completed, Failed, or Cancelled status are not re-dispatched, and ensure a warning/informational log entry is emitted when a non‑actionable job is encountered, updating the IJobStore.GetByJobNameAsync contract if needed.
#55 Add unit or integration tests that cover the duplicate-delivery scenario to verify that non-actionable jobs are not re-dispatched. The diff shows only production code changes; no new or modified test files are present to cover the duplicate-delivery scenario.

Possibly linked issues

  • #EF Core DateTime ValueConverter & DaprJobExecutionBridge status-aware lookup: PR adds IClock-backed DateTime converters and active-only job lookup, fulfilling both DateTime and Dapr bridge requirements.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Warning

Rate limit exceeded

@yilmaztayfun has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 58 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 22 minutes and 58 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ced1782-5ed7-4c6f-acdd-ee5bb458320d

📥 Commits

Reviewing files that changed from the base of the PR and between b69519a and a43557c.

📒 Files selected for processing (1)
  • framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Repositories/IJobStore.cs
📝 Walkthrough

Walkthrough

Changes introduce active-state filtering for background jobs, add clock-based DateTime normalization for EF Core entities, enhance job idempotency checks, and improve error handling in domain event dispatching. Job lookup now filters by active status; job scheduling enables overwrite mode; domain events include retry logic on publish failures.

Changes

Cohort / File(s) Summary
Background Job Status Property & Filtering
BBT/Aether/Domain/Entities/BackgroundJobInfo.cs, BBT/Aether/Domain/Repositories/IJobStore.cs, BBT/Aether/Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs
Added IsActive computed property to BackgroundJobInfo to identify jobs in Scheduled or Running state. Updated GetByJobNameAsync documentation and implementation to explicitly filter for active jobs only, changing lookup logic from ID-based to job-name-based filtering.
Job Execution & Dispatch Logic
BBT/Aether/BackgroundJob/Dapr/DaprJobExecutionBridge.cs, BBT/Aether/BackgroundJob/Dapr/DaprJobScheduler.cs, BBT/Aether/BackgroundJob/JobDispatcher.cs
Changed missing-job condition logging from error to warning in execution bridge. Enabled overwrite: true in Dapr job scheduling. Extended idempotency check to treat Failed jobs as already processed, preventing reprocessing alongside existing Completed and Cancelled statuses.
Aspect & Marker Interface Configuration
BBT/Aether/Aspects/Uow/UnitOfWorkAspectProvider.cs
Commented out default marker interface IApplicationService so it is no longer included by default in the marker interface set.
EF Core Clock Integration
BBT/Aether/Domain/EntityFrameworkCore/AetherDbContext.cs, BBT/Aether/Domain/EntityFrameworkCore/Modeling/AetherEntityTypeBuilderExtensions.cs, BBT/Aether/Domain/EntityFrameworkCore/ValueComparers/ClockDateTimeValueConverter.cs, BBT/Aether/Domain/EntityFrameworkCore/ValueComparers/ClockDateTimeOffsetValueConverter.cs
Added optional IClock parameter throughout EF Core configuration chain. Introduced clock-based value converters for DateTime and DateTimeOffset properties to apply UTC normalization consistently on both read and write paths.
Domain Event Dispatch Error Handling
BBT/Aether/Domain/EntityFrameworkCore/DomainEventDispatcher.cs
Wrapped outbox publish operation in try/catch to log failures without propagating exceptions, allowing subsequent event envelopes to continue processing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Issue #55: The changes directly implement active-status-aware background job lookup, IsActive property addition, and job-name-based filtering as described in the issue.

Possibly related PRs

  • PR #31: Overlapping modifications to DaprJobExecutionBridge and JobDispatcher background-job processing components.
  • PR #50: Concurrent modifications to BackgroundJobInfo, EfCoreJobStore job lookups, and JobDispatcher idempotency logic.
  • PR #16: Related changes to DomainEventDispatcher error handling and event publication behavior.

Suggested labels

enhancement

Suggested reviewers

  • middt
  • darcoakk

Poem

🐰 A rabbit hops through job queues bright,
Status flags glow Scheduled or Running light,
Clocks tick in UTC so true,
No duplicate work, just one pass through,
Errors caught gently, onward we go! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the two primary changes: EF Core DateTime normalization via ValueConverter and job store API simplification by consolidating GetByJobNameAsync.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 55-ef-core-datetime-valueconverter-for-iclock-normalization-daprjobexecutionbridge-status-aware-lookup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In DomainEventDispatcher, the new catch block for useOutboxDirectly logs and then swallows PublishAsync failures; consider whether these errors should propagate (or be aggregated) to avoid silently losing outbox messages when a write fails.
  • The new ClockDateTimeValueConverter and ClockDateTimeOffsetValueConverter types live under the ...ValueComparers namespace even though they are converters; renaming the namespace (or folder) to ValueConverters would better reflect their purpose and make discovery easier.
  • The XML summary on IJobStore.GetByJobNameAsync reads "Retrieves active background job information by the job name to active"; consider rephrasing to something like "Retrieves active background job information by job name" to avoid confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `DomainEventDispatcher`, the new catch block for `useOutboxDirectly` logs and then swallows `PublishAsync` failures; consider whether these errors should propagate (or be aggregated) to avoid silently losing outbox messages when a write fails.
- The new `ClockDateTimeValueConverter` and `ClockDateTimeOffsetValueConverter` types live under the `...ValueComparers` namespace even though they are converters; renaming the namespace (or folder) to `ValueConverters` would better reflect their purpose and make discovery easier.
- The XML summary on `IJobStore.GetByJobNameAsync` reads "Retrieves active background job information by the job name to active"; consider rephrasing to something like "Retrieves active background job information by job name" to avoid confusion.

## Individual Comments

### Comment 1
<location path="framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Repositories/IJobStore.cs" line_range="39-41" />
<code_context>

     /// <summary>
-    /// Retrieves background job information by the job name (external scheduler identifier).
+    /// Retrieves active background job information by the job name to active (external scheduler identifier).
     /// </summary>
     /// <param name="jobName">The unique job name used by the external scheduler (e.g., "send-email-order-123").</param>
</code_context>
<issue_to_address>
**nitpick (typo):** Doc comment has a small wording issue that may confuse readers.

The wording "by the job name to active" is confusing. Consider something like: "Retrieves active background job information by the job name (external scheduler identifier)." to clearly convey that this method filters to active jobs.

```suggestion
    /// <summary>
    /// Retrieves active background job information by the job name (external scheduler identifier).
    /// </summary>
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements UTC normalization for date-time properties using EF Core value converters and an IClock abstraction. It also enhances background job management by refining idempotency checks, updating job retrieval logic to focus on active states, and transitioning to name-based lookups in the job store. Feedback highlights a potential primary key conflict during job updates in EfCoreJobStore, the presence of commented-out code in the aspect provider, a documentation typo in the job store interface, and concerns regarding the suppression of exceptions during domain event publication which could lead to data inconsistency.

// Update existing job
jobInfo.ModifiedAt = DateTime.UtcNow;
jobInfo.ModifiedAt = DateTime.UtcNow;
_dbContext.Entry(existingJob).CurrentValues.SetValues(jobInfo);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using SetValues(jobInfo) will attempt to copy the Id property from jobInfo to existingJob. Since existingJob is a tracked entity and Id is its primary key, EF Core will throw an exception if the IDs differ (which is likely when performing an upsert by name with a newly instantiated jobInfo). You should ensure the primary key is not modified during the update.

            _dbContext.Entry(existingJob).CurrentValues.SetValues(jobInfo);
            _dbContext.Entry(existingJob).Property(x => x.Id).IsModified = false;

private readonly static HashSet<string> MarkerInterfaceNames = new()
{
"BBT.Aether.Application.IApplicationService"
//"BBT.Aether.Application.IApplicationService"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Avoid leaving commented-out code in the repository. If the IApplicationService marker is no longer required for automatic Unit of Work application, it should be removed to maintain code cleanliness.


/// <summary>
/// Retrieves background job information by the job name (external scheduler identifier).
/// Retrieves active background job information by the job name to active (external scheduler identifier).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The documentation contains a typo: 'to active'. It should be corrected for clarity.

    /// Retrieves active background job information by the job name (external scheduler identifier).

Comment on lines +49 to +54
catch (Exception ex)
{
logger.LogError(ex,
"Failed to publish domain event to outbox: {EventType}. Continuing with remaining events",
metadata.EventType.Name);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Swallowing all exceptions during domain event publication to the outbox can lead to silent data inconsistencies. If an event fails to be written to the outbox, it usually indicates a failure that should prevent the transaction from proceeding, or at least be handled more strictly than just logging and continuing with other events.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 12, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

@yilmaztayfun yilmaztayfun merged commit 8ab8f8e into master Apr 12, 2026
3 checks passed
@sonarqubecloud
Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EF Core DateTime ValueConverter for IClock normalization & DaprJobExecutionBridge status-aware lookup

1 participant