Problem
For Queueables the framework owns the execution lifecycle via QueueableJob + the chain finalizer, which is what makes features like retry (#34) possible. For Batchable and Schedulable it does not: Async.batchable(Object job) and Async.schedulable(Schedulable job) accept the standard Database.Batchable / Schedulable interfaces and hand them straight to Database.executeBatch / System.schedule unwrapped.
Because the consumer's class implements the raw platform interface, the framework has zero runtime hook into how the job runs — no place to observe failures, capture results, or retry. This is the gap called out in ASYNC-0002 ("we just pass the standard Batchable/Schedulable interfaces, which means we have no power over how the job runs").
Proposal
Introduce a framework-owned abstract base for batch (mirroring QueueableJob), so the framework controls execute/finish and can layer on retry, result capture, and config:
public abstract class BatchableJob implements Database.Batchable<SObject> {
public abstract Iterable<SObject> start(Database.BatchableContext bc);
public abstract void work(Database.BatchableContext bc, List<SObject> scope);
// framework owns execute()/finish(): scope-failure capture, retry, AsyncResult__c
}
Async.batchable(new MyBatch())
.scopeSize(200)
.retryFailedScopes(2)
.enqueue();
Design notes / feasibility
- Batch retry semantics differ from Queueable. You retry failed scopes, not the whole job. Natural hooks:
Database.RaisesPlatformEvents + BatchApexErrorEvent — capture which scopes failed and why, then re-submit a batch over just those records.
- Or re-
executeBatch from finish() for whole-job retry on transient failure.
- Schedulable "retry" mostly doesn't map — a schedule firing failure is really a reschedule, already largely covered by
CronBuilder / SchedulableManager. Likely out of scope; revisit if there's demand.
- This is a new subsystem, not a small change — it adds an abstract base + wrapper execution path + (likely) a new platform-event-driven failure capture. Should be planned as its own milestone.
- Backward compatibility: keep
Async.batchable(Object) accepting raw Database.Batchable as-is; the new BatchableJob base is additive opt-in.
Open Questions
- Whole-job retry vs scope-level retry vs both?
- Use
BatchApexErrorEvent (more moving parts, finer-grained) or simpler finish()-driven re-submit first?
- Do we want a unified result/observability story across Queueable + Batchable (
AsyncResult__c)?
- Is Schedulable abstraction worth it at all, or close as "won't do"?
Acceptance Criteria (high level — to refine)
Context: split out from ASYNC-0002 / #34 (Queueable retry). Tracked separately because it's a larger design effort than the finalizer-based Queueable retry.
Problem
For Queueables the framework owns the execution lifecycle via
QueueableJob+ the chain finalizer, which is what makes features like retry (#34) possible. For Batchable and Schedulable it does not:Async.batchable(Object job)andAsync.schedulable(Schedulable job)accept the standardDatabase.Batchable/Schedulableinterfaces and hand them straight toDatabase.executeBatch/System.scheduleunwrapped.Because the consumer's class implements the raw platform interface, the framework has zero runtime hook into how the job runs — no place to observe failures, capture results, or retry. This is the gap called out in ASYNC-0002 ("we just pass the standard Batchable/Schedulable interfaces, which means we have no power over how the job runs").
Proposal
Introduce a framework-owned abstract base for batch (mirroring
QueueableJob), so the framework controlsexecute/finishand can layer on retry, result capture, and config:Design notes / feasibility
Database.RaisesPlatformEvents+BatchApexErrorEvent— capture which scopes failed and why, then re-submit a batch over just those records.executeBatchfromfinish()for whole-job retry on transient failure.CronBuilder/SchedulableManager. Likely out of scope; revisit if there's demand.Async.batchable(Object)accepting rawDatabase.Batchableas-is; the newBatchableJobbase is additive opt-in.Open Questions
BatchApexErrorEvent(more moving parts, finer-grained) or simplerfinish()-driven re-submit first?AsyncResult__c)?Acceptance Criteria (high level — to refine)
BatchableJobabstract base withstart/workoverridesAsync.batchable(Object)raw-interface path unchanged (backward compatible)