{"id":15039593,"url":"https://github.com/rspeele/rezoom","last_synced_at":"2025-08-01T05:37:52.169Z","repository":{"id":144135525,"uuid":"60657014","full_name":"rspeele/Rezoom","owner":"rspeele","description":"Implements a resumption monad for .NET targeting data access with automatic batching and caching.","archived":false,"fork":false,"pushed_at":"2019-03-21T10:31:00.000Z","size":2193,"stargazers_count":109,"open_issues_count":2,"forks_count":5,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-10T00:06:37.533Z","etag":null,"topics":["batching","caching","data","dot-net","dotnet","monad"],"latest_commit_sha":null,"homepage":null,"language":"F#","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/rspeele.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2016-06-08T00:51:30.000Z","updated_at":"2024-11-28T16:32:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"05490a6c-f13d-42d3-af89-9cbe38a233a1","html_url":"https://github.com/rspeele/Rezoom","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rspeele%2FRezoom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rspeele%2FRezoom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rspeele%2FRezoom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rspeele%2FRezoom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rspeele","download_url":"https://codeload.github.com/rspeele/Rezoom/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248131317,"owners_count":21052819,"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":["batching","caching","data","dot-net","dotnet","monad"],"created_at":"2024-09-24T20:43:24.108Z","updated_at":"2025-04-10T00:06:44.396Z","avatar_url":"https://github.com/rspeele.png","language":"F#","readme":"# What's this?\n\nRezoom is a library intended to reduce the pain of dealing with data that lives\nacross a latency boundary.\n\n![Horrors of latency](https://raw.githubusercontent.com/rspeele/Rezoom/master/doc/images/Latency.png)\n\nCommon examples of this nightmare include SQL databases, NoSQL databases, and\nweb APIs.\n\nIt tends to be hard to write abstractions over these data sources because the\nround-trip time dominates all other performance concerns, and it's impossible to\noptimize without breaking your API.\n\nIf somebody is calling `GetUserDetails(userId)` in a loop for 500 different\nusers, you really can't help them other than by convincing them to switch to\n`GetMultipleUserDetails(all500userIds)`.\n\nRezoom lets you write little units of business logic called `Plan`s (like the\naforementioned `GetUserDetails`), which you can then glue together into larger\nplans. It'll handle converting the 500 independent `GetUserDetails` calls into\none `GetMultipleUserDetails` call. It also de-duplicates redundant requests: for\nexample if multiple functions need to query for the current user's permissions,\nonly one such query will actually be executed.\n\n# Show me an example so I know whether to care\n\nHere's some contrived example code. You can write a Rezoom wrapper library for\nany data source, but it comes with one called Rezoom.SQL that statically\ntypechecks SQL and infers its caching behavior.\n\n```fsharp\ntype GetPerson = SQL\u003c\"select * from People where Id = @id\"\u003e\ntype GetCompany = SQL\u003c\"select * from Companies where Id = @id\"\u003e\n\n/// Gets a person and their employer. This implementation takes 2 round trips.\nlet getPersonAndEmployer (personId : int) : (GetPerson.Row * GetCompany.Row) Plan =\n    plan {\n        // One round trip.\n        let! person = GetPerson.Command(id = personId).ExactlyOne()\n        // Another round trip.\n        let! employer = GetCompany.Command(id = person.EmployerId).ExactlyOne()\n        return (person, employer)\n    }\n\nlet example =\n    plan {\n        // Two round-trips: one to get both users, another to get both employers.\n        // If the two users have the same employer, the 2nd round-trip will only include one SELECT statement.\n        let! (user1, employer1), (user2, employer2) =\n            getPersonAndEmployer 1, getPersonAndEmployer 2\n        printfn \"%s is employed by %, %s by %s\"\n            user1.Name, employer1.Name\n            user2.Name, employer2.Name\n\n        // Two more round trips: again, the user queries all share one, and the employer queries share the next.\n        for id in batch [ 3; 4; 5; 6 ] do\n            let! user, employer = getPersonAndEmployer id\n            printfn \"%s is employed by %s\" user.Name, employer.Name\n    }\n\n```\n\nPretty neat, huh? How about this?\n\n```fsharp\ntype UpdateCompanies = SQL\u003c\"update Companies set LastUpdatedUtc = SysUtcDateTime() where Id = 27\"\u003e\n\nlet example2 =\n    plan {\n        // Two round-trips: one for the user, one for the employer.\n        let! (user1, employer1) =\n            getPersonAndEmployer 1\n\n        // Zero round trips, because both query results are cached.\n        let! (user1Again, employer1Again) =\n            getPersonAndEmployer 1\n\n        // Invalidates the cache for all queries dependent on the Companies table.\n        // This is automatically deduced by the SQL\u003c\"query string\"\u003e type provider.\n        do! UpdateCompanies.Command().Plan()\n\n        // One round-trip: user is still cached, but we have to reload the employer.\n        let! (user1Again, employer1Again) =\n            getPersonAndEmployer 1\n\n        return 0\n    }\n```\n\n\"Now, hang on a minute,\" you might be asking your computer screen. \"Where's this\nstuff being cached? What if some other code updates a table and now the cache is\ninvalid?\"\n\nWhen you write a `plan` block nothing happens until you execute it with\n`Execution.execute(plan)`. The cache is local to that execution, which is also\nimplicitly wrapped in a transaction. So, the cache is not for keeping\ninformation across, say, multiple requests to your website.\n\nIt's to eliminate the temptation to explicitly pass around loaded data,\ncluttering your API in an attempt to save queries.\n\nThe idea is to let you write very fine-grained APIs that take the bare minimum\nof information (like `deleteSlide : SlideId -\u003e unit Plan`) but can be composed\ntogether without creating a performance nightmare.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frspeele%2Frezoom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frspeele%2Frezoom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frspeele%2Frezoom/lists"}