{"id":15567284,"url":"https://github.com/scolladon/apex-async-linkable","last_synced_at":"2026-01-08T00:04:23.353Z","repository":{"id":45351714,"uuid":"160425717","full_name":"scolladon/apex-async-linkable","owner":"scolladon","description":"Allow to chain every kind of async apex job","archived":false,"fork":false,"pushed_at":"2023-06-26T15:35:14.000Z","size":1094,"stargazers_count":37,"open_issues_count":0,"forks_count":9,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-02-03T15:55:48.754Z","etag":null,"topics":["apex","async-jobs","chain","chaining","salesforce"],"latest_commit_sha":null,"homepage":null,"language":"Apex","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scolladon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-12-04T22:14:46.000Z","updated_at":"2024-09-27T06:19:07.000Z","dependencies_parsed_at":"2024-12-09T04:41:15.416Z","dependency_job_id":"14a20b0d-1c2f-44fa-89ee-931fa24583a1","html_url":"https://github.com/scolladon/apex-async-linkable","commit_stats":{"total_commits":22,"total_committers":4,"mean_commits":5.5,"dds":"0.13636363636363635","last_synced_commit":"78054be3e34751ae2d4055783d056781a2b81b1a"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scolladon%2Fapex-async-linkable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scolladon%2Fapex-async-linkable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scolladon%2Fapex-async-linkable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scolladon%2Fapex-async-linkable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scolladon","download_url":"https://codeload.github.com/scolladon/apex-async-linkable/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246145014,"owners_count":20730494,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apex","async-jobs","chain","chaining","salesforce"],"created_at":"2024-10-02T17:10:36.529Z","updated_at":"2025-10-14T12:10:24.447Z","avatar_url":"https://github.com/scolladon.png","language":"Apex","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Apex AsyncLinkable ![Build](https://github.com/scolladon/apex-async-linkable/actions/workflows/main.yml/badge.svg) [![codecov](https://codecov.io/gh/scolladon/apex-async-linkable/branch/main/graph/badge.svg?token=DFHDV3OCIS)](https://codecov.io/gh/scolladon/apex-async-linkable)\n\nThis library helps to deal with asynchronous software architecture issues.\nThe approach of the library is to abstract the concept of asynchronous transactions and to provide a layer to deal with chaining and spawning, letting the customer determine the responsibility to write business rules code.\n\nThe library helps chaining together each kind of asynchronous processes (Batchable, Schedulable, Queueable, Future).\n\nIt ensures they will be executed sequentially.\nThe library comes with features (Chain manager, Iterator, Visitor) and mechanisms (Executor pattern) to deal with low level orchestration.\n\nThe library provides all the classes required to chain all kinds of asynchronous jobs.\nIt improves the reusability of asynchronous implementations by making them agnostic of their execution context.\n\nThe library will help you chain your asynchronous jobs together, without the need for them to know each other, or to take care of the context.\n\nFor example, let's say we have a Service on Case dealing with business rules. You need to use this service inside a Batch.\nYou don't want to care about the specificity of its implementation.\nYou don't want to have to change your implementation if it does not work anymore in asynchronous process\n\nThe owner of the service does not want to care about other consumer if he changes its implementation (callout, queueable, platform event, etc)\n\nThe library helps with asynchronous process specific governor limits. A chained job will not be spawned all at once in the same transaction but one-by-one, jobs will be protected against the \"Too many queueable jobs added to the queue: 2\" exception when spawned inside an asynchronous process.\n\nUsing the global `ChainManager` to chain and spawn async job ensure they will be spawn if possible and when appropriate.\n\nThe library is also an efficient solution to Flex Queue management, instead of being added to the Flex Queue, the asynchronous job are maintained on the heap and added to the Flex Queue when the previous process finishes.\n\nLet's say you have a transaction where three (3) queueable are enqueued-- they will be put in the Flex Queue and will take three (3) places. They will be spawned in the order of enqueueing.\nBy chaining the job together, only the first will be on the Flex Queue. When executed it will spawn the next one and so on.\n\nIt is like a lazy queueing mechanism and can drastically relieve Flex Queue load and scale to large complex asynchronous implementation.\n\n## Use cases\n\nUse this library when the Flex Queue happens to full often and refuses async job.\nUse this library when you need to use a service spawning asynchronous job inside asynchronous job, and you don't want to refactor the service you are calling (not the owner of the service, not related to the requirement you are currently solving).\n\n/!\\ The library will not diminish the number of asynchronous job spawned per day!\nBut it will help releasing the pressure on the Flex Queue.\n\n## Installation\n\n```bash\n$ sfdx force:source:deploy -p chain/src/lib\n```\n\n## Usage\n\nAsyncLinkable implementation is a linked list of special convenient async type\nholding chaining and spawning properties abstracted to the developer.\n\n### How to use the `ChainManager`\n\nUse the global `ChainManager.instance` to\n\n- register a process responsible for the execution of the chain\n\n```apex\nChainManager.instance.registerExecutor('My Process');\n```\n\n- add new async job to the chain.\n\n```apex\nfinal AsyncLinkable asyncJob = new MyAsyncJob();\nChainManager.instance.add(asyncJob);\n```\n\n- spawn the chain\n\n```apex\nChainManager.instance.startChain();\n// Or\nChainManager.instance.startChain('My Process');\n```\n\n### Async type implementation example\n\nExtend the AsyncLinkable type of your choice and then implement the `job` method.\nThe `job` method will be called by the framework when the async processus will be spawn.\n_`start` method must also be overridden for the BatchLink and ScheduleBatchLink class_\n\nThe `job` method will contain your business logic. It can then access the private attributes of your class and the protected ones of the base class.\n\nIf you need some extra interface to make your code work, it is up to you to add them (such as `Database.Stateful`, `Database.AllowsCallouts`, etc).\n\nExample for Batchable:\n\n```apex\n// Subclass BatchLink for example\npublic class BatchLink_EXAMPLE extends BatchLink {\n  public override Database.QueryLocator start(\n    Database.BatchableContext bc\n  ) {\n    return Database.getQueryLocator('select id from account limit 1');\n  }\n\n  public BatchLink_EXAMPLE() {\n    super();\n  }\n\n  protected override void job() {\n    System.Debug('BatchLink_EXAMPLE');\n\n    // Acces records queried by the start method\n    final List\u003cAccount\u003e accounts = (List\u003cAccount\u003e) this.scope;\n  }\n}\n```\n\nExample for Scheduled Batchable:\n\n```apex\n// Subclass ScheduleBatchLink for example\npublic class ScheduleBatchLink_EXAMPLE extends ScheduleBatchLink {\n  public override Database.QueryLocator start(\n    Database.BatchLinkableContext bc\n  ) {\n    return Database.getQueryLocator('select id from account limit 1');\n  }\n\n  public ScheduleBatchLink_EXAMPLE() {\n    super();\n  }\n\n  protected override void job() {\n    System.Debug('ScheduleBatchLink_EXAMPLE');\n\n    // Acces records queried by the start method\n    final List\u003cAccount\u003e accounts = (List\u003cAccount\u003e) this.scope;\n  }\n}\n```\n\nExample for Schedulable:\n\n```apex\n// Subclass ScheduleLink for example\npublic class ScheduleLink_EXAMPLE extends ScheduleLink {\n  public ScheduleLink_EXAMPLE() {\n    super();\n  }\n\n  protected override void job() {\n    System.Debug('ScheduleLink_EXAMPLE');\n  }\n}\n```\n\nExample for Queueable:\n\n```apex\n//Subclass QueueLink for example\npublic class QueueLink_EXAMPLE extends QueueLink {\n  public QueueLink_EXAMPLE() {\n    super();\n  }\n\n  protected override void job() {\n    System.Debug('BatchLink_EXAMPLE');\n  }\n}\n```\n\nExample for Future:\n\n```apex\n//Subclass FutureLink for example\npublic class FutureLink_EXAMPLE extends FutureLink {\n  public FutureLink_EXAMPLE() {\n    super();\n  }\n\n  protected override void job() {\n    System.Debug('FutureLink_EXAMPLE');\n  }\n}\n```\n\nExample chaining all of those;\n\n```apex\n// Chain both and execute them\npublic class Service {\n  public static void doService() {\n    ChainManager.instance\n      .add(new BatchLink_EXAMPLE())\n      .add(new ScheduleBatchLink_EXAMPLE('scheduled batch in 1min', 1))\n      .add(new ScheduleLink_EXAMPLE('scheduled', '0 0 * * * ?'))\n      .add(new QueueLink_EXAMPLE())\n      .add(new FutureLink_EXAMPLE())\n      .startChain();\n  }\n}\n```\n\n### Executor principle\n\nThe executor pattern used in this library help to determine what is the parent process responsible for the global execution of the chain.\nIt avoid the code to spawn the chain too early and ensure it is spawn in the end.\n\n#### Simple example\n\nLet's say we have a method calling a service and then spawning a queueable:\n\n```apex\n\nclass Service {\n public static void execute() {\n   ChainManager.instance.add(new AsyncJob()).startChain();\n }\n}\n\nclass Controller {\n public void simpleExample() {\n   Service.execute(); // spawn an async job\n   ChainManager.instance.add(new MyQueue()).startChain();// spawn an async job\n }\n}\n```\n\nLet's break it down line by line:\n\n- add AsyncJob startChain without executor =\u003e no executor registered =\u003e spawn it\n- add MyQueue\n- startChain with executor =\u003e no executor registered =\u003e spawn it\n\nAs the controller does not have register an executor, the chain was spawned twice.\nIt can fail when `simpleExample` is executed inside a queueable because it is possible to spawn an async job only once in this kind of context.\n\nWhat we should do is register an executor.\nIt says: spawn the chain only if the executor provided is the same as the one registered.\nThis way the code does not have to know if it is called first and need to spawn the chain. This way it is possible to compose business rule with existing service, without knowing how it is implemented (async or not) and without caring about future evolution (adding async processing inside it):\n\nLet's say we have a method calling a service and then spawning a queueable:\n\n```apex\n\nclass Service {\n public static void execute() {\n   // Start chain does not specify the executor\n   ChainManager.instance.add(new AsyncJob()).startChain();\n }\n}\n\nclass Controller {\n private static final String ControllerExecutor = 'ControllerExecutor';\n public void simpleExample() {\n   ChainManager.instance.registerExecutor(ControllerExecutor);\n   Service.execute(); // chain the async but does not spawn it\n   ChainManager.instance.add(new MyQueue()).startChain(ControllerExecutor);// Enqueue an async job chained with the one from service.execute\n }\n}\n```\n\nLet's break it down line by line:\n\n- register executor 'ControllerExecutor'\n- add AsyncJob\n- startChain without executor =\u003e null != registered executor =\u003e do nothing\n- add MyQueue\n- startChain with executor =\u003e 'ControllerExecutor' == 'ControllerExecutor' =\u003e spawn the first element from the list\n\n#### More complex Trigger example\n\nLook at the recipes folder complex example.\nThere is a trigger and the goal is to chain together treatment from before and after events. And also chaining every intermediate async processes triggered by services called by the trigger handler.\n\nUsing an executor allow us to easily write the code without having to care much if service methods called will spawn async process.\nThis makes the code safer to run in async process.\n\nPer example updating a list of account inside a Queue will be possible with this trigger without having to check if it is run inside a queueable job or not to spawn the async processes running business logic.\n\n#### Debounce example\n\nLook at the recipes folder `debounce` example\nHere the code check for other instance of the same `AsyncApexJob` and spawn the job only if it is not already planned.\nIf the job is spawned, then it is put in the back of the flex queue to debounce it.\nAnother approach could be to discard it and reschedule it using the same spawn method to reuse potential delay implemented (queue, schedulable job, schedulable batch).\n\n#### Throttle example\n\nLook at the recipes folder `throttle` example\nHere the code check for other instance of the same `AsyncApexJob` and spawn the job only if it is not already planned\n\n### Iterate over a chain\n\nUse the `AsyncLinkIterator` as a convenient way to iterate over an `AsyncLink`:\n\n```apex\nfinal AsyncLinkable queueLink = new MyQueueLink();\nfinal AsyncLinkable anotherQueueLink = new MyOtherQueueLink();\nfinal AsyncLinkable yetAnotherQueueLink = new MyYetOtherQueueLink();\nqueueLink.setNext(anotherQueueLink);\nanotherQueueLink.setNext(yetAnotherQueueLink);\nfinal Iterator\u003cAsyncLinkable\u003e iterator = new AsyncLinkIterator(queueLink);\nAsyncLinkable currentLink;\nwhile (iterator.hasNext()) {\n  currentLink = iterator.next();\n  // Do stuff\n}\n```\n\n### Implement the Visitor pattern\n\nUse the `LinkVisitor` interface to create an AsyncLinkableVisitor and implement custom logic to the whole linked list.\n`ChainManager.unchain` and `ChainManager.getLastLink` implementation is done using this pattern.\n\nHere is an example to extract from the chain every element of a certain type:\n\n```apex\nprivate class ExtractElement implements AsyncLinkable.LinkVisitor {\n  private AsyncLinkable link;\n  private System.Type typeToExtract;\n\n  public ExtractElement(final System.Type typeToExtract) {\n    this.typeToExtract = typeToExtract;\n  }\n\n  public void visit(final AsyncLinkable link) {\n    this.link = link;\n  }\n\n  public List\u003cAsyncLinkable\u003e getLinks() {\n    final List\u003cAsyncLinkable\u003e links = new List\u003cAsyncLinkable\u003e();\n    if (this.link == null) {\n      return links;\n    }\n    final Iterator\u003cAsyncLinkable\u003e iterator = new AsyncLinkIterator(this.link);\n    AsyncLinkable currentLink;\n    while (iterator.hasNext()) {\n      currentLink = iterator.next();\n      if (\n        String.valueOf(currentLink).split(':')[0] ==\n        this.typeToExtract.getName()\n      ) {\n        links.Add(currentLink);\n      }\n    }\n    return links;\n  }\n}\n```\n\n## Software Architecture\n\n![Apex Linkable class diagram](resources/class-diagram.png)\n\n## Versioning\n\n[SemVer](http://semver.org/) is used for versioning.\n\n## Authors\n\n- **Sebastien Colladon** - _Initial work_ - [scolladon](https://github.com/scolladon)\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscolladon%2Fapex-async-linkable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscolladon%2Fapex-async-linkable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscolladon%2Fapex-async-linkable/lists"}