{"id":18253984,"url":"https://github.com/cptrodgers/aj","last_synced_at":"2025-04-06T02:08:06.065Z","repository":{"id":211686144,"uuid":"729706219","full_name":"cptrodgers/aj","owner":"cptrodgers","description":"Rust - background jobs.","archived":false,"fork":false,"pushed_at":"2024-10-29T14:45:58.000Z","size":2996,"stargazers_count":40,"open_issues_count":2,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-10-29T17:34:30.930Z","etag":null,"topics":["actix","background-jobs","cronjob","retry","rust","scheduled-tasks"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/aj","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cptrodgers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE-APACHE","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":"2023-12-10T04:42:51.000Z","updated_at":"2024-10-27T05:04:21.000Z","dependencies_parsed_at":"2023-12-23T16:27:57.116Z","dependency_job_id":"3c298965-e431-4b7c-b891-b6743f873f76","html_url":"https://github.com/cptrodgers/aj","commit_stats":null,"previous_names":["cptrodgers/aj","zenclasshq/aj","openexamhq/aj","tikigai/aj","ikigai-hq/aj"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cptrodgers%2Faj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cptrodgers%2Faj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cptrodgers%2Faj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cptrodgers%2Faj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cptrodgers","download_url":"https://codeload.github.com/cptrodgers/aj/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247423514,"owners_count":20936626,"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":["actix","background-jobs","cronjob","retry","rust","scheduled-tasks"],"created_at":"2024-11-05T10:09:32.706Z","updated_at":"2025-04-06T02:08:06.031Z","avatar_url":"https://github.com/cptrodgers.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aj\n![ci status](https://github.com/cptrodgers/aj/actions/workflows/test-and-build.yml/badge.svg)\n\nAj is a simple, customizable, and feature-rich background job processing library for Rust.\nIt can work with any runtime by running new actix-rt in a separated thread if it detects that an actix-rt runtime is not present.\n\n## Install\n\n```toml\naj = \"0.7.0\"\nserde = { version = \"1.0.64\", features = [\"derive\"] } # Serialize and deserialize the job\nactix-rt = \"2.2\" # Actor model runtime engine\n```\n\n## Quick start\n\n```rust\nuse aj::job;\n\n#[job]\nasync fn hello(name: String) {\n    println!(\"Hello {name}\");\n}\n\n#[aj::main]\nasync fn main() {\n    // AJ will be backed by run in-memory backend.\n    // If you wish to use redis as the backend for aj.\n    // AJ::start(aj::Redis::new(\"redis://localhost:6379\"));\n    AJ::quick_start();\n    // Fire and forget the job. No gruantee job is queued\n    hello::just_run(\"Rodgers\".into());\n    // Or waiting job is queued\n    hello::run(\"AJ\".into()).await;\n\n    // Sleep 1 sec to view the result from job (if you want to wait the job run)\n    // sleep(Duration::from_secs(1)).await;\n}\n```\n\n## Features \u0026 Usage\n\n- [Create Job](#declare-a-job)\n- [Schedule Job](#scheduled-job)\n- [Cron Job](#cron-job)\n- [Update Job](#update-job)\n- [Cancel Job](#cancel-job)\n- [Get Job](#get-job)\n- [Retry](#retry)\n  - Interval Retry\n  - Backoff exponential retry\n- [Plugin](#plugin)\n- [Config Queue](#config)\n- Custom Backend\n- DAG (Coming soon)\n- Distributed Mode (Coming soon)\n- Monitoring \u0026 Web Admin UI\n\n### Declare a Job\n\nWe support 2 ways to define a job. Macro and structure.\n\n** Use macro #[job] ([Full example](https://github.com/cptrodgers/aj/blob/master/examples/normal/src/macro_job.rs)) macro **\n\n```rust\n#[job]\nasync fn hello(name: String) {\n    println!(\"Hello {name}\");\n}\n```\n\n**Structure**\n\nYou can declare a Background Job by use Struct and implement trait `Executable` for that struct.\n[Full example](https://github.com/cptrodgers/aj/blob/master/examples/normal/src/print_job.rs)\n\n```rust\n#[derive(BackgroundJob, Serialize, Deserialize, Debug, Clone)]\npub struct Print {\n    number: i32,\n}\n\n#[async_trait]\nimpl Executable for Print {\n    type Output = ();\n\n    async fn execute(\u0026self, _context: \u0026JobContext) -\u003e Self::Output {\n        println!(\"Hello Job {}, {}\", self.number, get_now());\n    }\n}\n\n#[main]\nasync fn main() {\n    // Start AJ engine\n    AJ::quick_start();\n\n    let job_id = Print { number: 1 }\n        .job()\n        .run()\n        .await\n        .unwrap();\n}\n```\n\n### Scheduled Job\n\n[Example](https://github.com/cptrodgers/aj/blob/master/examples/normal/src/schedule_job.rs)\nGiven that we have `Print` job.\n\n```rust\n// Delay 1 sec and run\nlet _ = Print { number: 1 }\n    .job()\n    .delay(Duration::seconds(1))\n    .run()\n    .await;\n\n// Schedule after 2 seconds\nlet _ = Print { number: 2 }\n    .job()\n    .schedule_at(get_now() + Duration::seconds(3))\n    .run()\n    .await;\n```\n\n\n### Cron Job\n[Example](https://github.com/cptrodgers/aj/blob/master/examples/normal/src/cron_job.rs)\n\n```rust\n// Cron, run this job every seconds\nlet _ = Print { number: 3 }\n    .job()\n    .cron(\"* * * * * * *\")\n    .run()\n    .await;\n```\n\n### Update Job\n\n[Example](https://github.com/cptrodgers/aj/blob/master/examples/normal/src/update_job.rs)\n\n```rust\n// Run cron job every secs\nlet job_id = Print { number: 1 }\n    .job()\n    .cron(\"* * * * * * *\")\n    .run()\n    .await\n    .unwrap();\n\n// Update print 1 -\u003e 2\nAJ::update_job(\u0026job_id, Print { number: 2 }, None)\n    .await\n    .unwrap();\n```\n\nUpdate Job Context (Such as retry logic, cron and schedule, etc)\n\n```rust\nAJ::update_job(\n  \u0026job_id,\n  Print { number: 2 },\n  aj::JobContext::default(), // Change this to apply new context\n)\n  .await\n  .unwrap();\n```\n\n### Cancel Job\n\n[Example](https://github.com/cptrodgers/aj/blob/master/examples/normal/src/cancel_job.rs)\n\n```rust\nlet result = AJ::cancel_job::\u003cPrint\u003e(\u0026job_id).await;\nlet success = result.is_ok();\n```\n\n### Get Job\n\n```rust\nlet job = AJ::get_job::\u003cPrint\u003e(\u0026job_id).await;\n```\n\n### Retry\n\n[Example](https://github.com/cptrodgers/aj/blob/master/examples/normal/src/retry_job.rs)\n\n#### Auto Retry\n\nFirst, you should declare the failed output via method `is_failed_output`.\nIf the result is true, then the job will retry (by following retry strategy)\n\n```rust\n#[async_trait]\nimpl Executable for Print {\n    type Output = Result\u003c(), String\u003e;\n\n    async fn execute(\u0026self, context: \u0026JobContext) -\u003e Self::Output {\n        println!(\"Hello {}, {}\", self.number, context.run_count);\n        Err(\"I'm failing\".into())\n    }\n\n    // Determine where your job is failed.\n    // For example, check job output is return Err type\n    async fn is_failed_output(\u0026self, job_output: Self::Output) -\u003e bool {\n        job_output.is_err()\n    }\n}\n```\n\n**Interval Strategy**\n\n```rust\nlet max_retries = 3;\nlet job = Print { number: 1 }\n    .job()\n    // Try to retry 3 times, retry after failed job 1 sec.\n    .retry(Retry::new_interval_retry(\n        Some(max_retries),\n        chrono::Duration::seconds(1),\n    ));\nlet _ = job.run().await;\n```\n\n**Exponential Strategy**\n\n```rust\nlet job = Print { number: 3 }\n    .job()\n    .retry(Retry::new_exponential_backoff(\n        Some(max_retries),\n        // Initial Backoff value\n        chrono::Duration::seconds(1),\n    ));\nlet _ = job.run().await.unwrap();\n```\n\n**Custom Strategy**\n\nTBD\n\n#### Manual Retry\n\nYou can also manually retry a 'Done' job (status: finished, failed, or cancelled).\nThis is useful for applications that have a UI allowing users to retry the job.\n\n```rust\nAJ::retry_job::\u003cPrint\u003e(\u0026job_id).await.unwrap();\n```\n\n### Plugin\n\n[Example](https://github.com/cptrodgers/aj/blob/master/examples/normal/src/plugin.rs)\n\n```rust\nuse aj::{async_trait, job::JobStatus, JobPlugin};\n\npub struct SamplePlugin;\n\n#[async_trait]\nimpl JobPlugin for SamplePlugin {\n    async fn change_status(\u0026self, job_id: \u0026str, job_status: JobStatus) {\n        println!(\"Hello, Job {job_id} change status to {job_status:?}\");\n    }\n\n    async fn before_run(\u0026self, job_id: \u0026str) {\n        println!(\"Before job {job_id} run\");\n    }\n\n    async fn after_run(\u0026self, job_id: \u0026str) {\n        println!(\"After job {job_id} run\");\n    }\n}\n\n#[aj::main]\nasync fn main() {\n    AJ::register_plugin(SamplePlugin).await.unwrap();\n}\n```\n\n### Config\n\n```rust\nAJ::update_work_queue(aj::queue:WorkQueueConfig {\n    // 50 ms will fetch job again\n    process_tick_duration: choro::Duration::milliseconds(50),\n    // Only process 10 jobs at time\n    max_processing_jobs: 10,\n}).await;\n```\n\n### Custom Backend (Both Broker and Storage)\nIf you wish to customize the backend of AJ, such as using Postgres, MySQL, Kafka, RabbitMQ, etc.,\nyou can implement the `Backend` trait and then use it in AJ.\n\n[In Memory Example](https://github.com/cptrodgers/aj/blob/master/aj_core/src/backend/mem.rs)\n\n```rust\npub YourBackend {\n...\n}\n\nimpl Backend for YourBackend {\n    ...\n}\n\n// Use it, just replace Redis by your backend.\nAJ::start(YourBackend::new());\n```\n\n\n### Distributed Mode (Run multiple AJ in many rust applications)\n\nIn Roadmap\n\n### DAG\n\nIn Roadmap\n\n### Monitoring \u0026 APIs\n\nIn Roadmap\n\n\n## LICENSE\n\n\u003csup\u003e\nLicensed under either of \u003ca href=\"LICENSE-APACHE\"\u003eApache License, Version\n2.0\u003c/a\u003e or \u003ca href=\"LICENSE-MIT\"\u003eMIT license\u003c/a\u003e at your option.\n\u003c/sup\u003e\n\n\u003cbr\u003e\n\n\u003csub\u003e\nUnless you explicitly state otherwise, any contribution intentionally submitted\nfor inclusion in aj by you, as defined in the Apache-2.0 license, shall be\ndual licensed as above, without any additional terms or conditions.\n\u003c/sub\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcptrodgers%2Faj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcptrodgers%2Faj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcptrodgers%2Faj/lists"}