{"id":17134356,"url":"https://github.com/brianium/clean-todos","last_synced_at":"2025-07-28T01:38:29.439Z","repository":{"id":146514840,"uuid":"97592794","full_name":"brianium/clean-todos","owner":"brianium","description":":clipboard: A todo example leveraging clean architecture in Clojure","archived":false,"fork":false,"pushed_at":"2024-04-12T22:20:02.000Z","size":95,"stargazers_count":88,"open_issues_count":0,"forks_count":9,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-07-26T18:53:02.678Z","etag":null,"topics":["clean-architecture","cli","clojure","clojurescript","re-frame","rest-api"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brianium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.ASL2.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,"zenodo":null}},"created_at":"2017-07-18T11:59:06.000Z","updated_at":"2025-01-05T05:04:37.000Z","dependencies_parsed_at":"2024-04-12T23:32:39.682Z","dependency_job_id":"537db9ee-e4a6-487a-91d8-148699ddca1d","html_url":"https://github.com/brianium/clean-todos","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/brianium/clean-todos","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fclean-todos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fclean-todos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fclean-todos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fclean-todos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brianium","download_url":"https://codeload.github.com/brianium/clean-todos/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brianium%2Fclean-todos/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267451232,"owners_count":24089298,"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","status":"online","status_checked_at":"2025-07-27T02:00:11.917Z","response_time":82,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["clean-architecture","cli","clojure","clojurescript","re-frame","rest-api"],"created_at":"2024-10-14T19:44:42.066Z","updated_at":"2025-07-28T01:38:29.413Z","avatar_url":"https://github.com/brianium.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Clean Todos\n\nAn example of clean architecture in Clojure. The goal of this project is to demonstrate\nthe ever elusive [clean architecture](https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html)\nin Clojure, and also serve as a gentle introduction to `clojure.spec` (for me and hopefully others).\n\nRunning the various deliveries should be a snap once you get [leiningen](https://leiningen.org/) installed.\n\nIf you are interested - please see the [retrospect](https://github.com/brianium/clean-todos#retrospect). One can only learn so much from the classic \"todo\" app, but I feel this app has given me a better understanding of the Clojure(Script) landscape as a whole.\n\nThe original version of this application inspired the [yoose](https://github.com/brianium/yoose) library - a library that facilitates use case centered applications. The current version of this app leverages yoose.\n\n\n## Delivery (i.e presentation for the app)\n\nThe `todos.delivery` namespace contains different delivery mechanisms for clean todos - i.e cli, web, etc..\n\n### todos.delivery.cli\n\nThis delivery provides a command line app for managing todos. Todos are persisted using sqlite.\n\n#### Usage\n\n```\nUsage:\n  todos command [options]\n\nOptions:\n  -s, --status STATUS  all  Todo status to filter on\n  -h, --help\n\nAvailable Commands:\n  create: Create a new todo\n  create todo-name\n\n  list: List todos\n  list --status=STATUS \u003ccompleted,active,all\u003e\n\n  toggle: Toggle todo status\n  toggle todo-id\n\n  delete: Permanently removes a todo\n  delete todo-id\n```\n\n#### Building\n\nThis delivery is built to a single executble named `todos` using the [lein binplus](https://github.com/BrunoBonacci/lein-binplus) plugin. Lein can then be used to build the cli delivery using the `cli` profile:\n\n```\n$ lein with-profile cli bin\n```\n\n### todos.delivery.api\n\nThe api delivery provides a simple restful interface for managing todos.\n\nThe following routes are supported:\n\n```\nGET    /todos{?status=completed,active} - status defaults to all todos\nPOST   /todos\nDELETE /todos{/id}\nPATCH  /todos{/id}\n```\n\n`POST` expects a JSON document of the form `{\"title\": \"string\", \"complete?\": boolean}`\n\n`PATCH` expects a similar document to `POST` with the difference that all keys are optional.\n\nAll inputs are validated via `clojure.spec.alpha/conform`.\n\n#### Running the server\n\nThe server is powered via [lein-ring](https://github.com/weavejester/lein-ring). Just include the `api`\nprofile when running:\n\n```\n$ lein api\n```\n\n### todos.delivery.web\n\nThe web delivery is a re-frame app that leverages the api delivery.\n\n#### Running the web application\n\nThe web app is run in a dev setting using [lein-figwheel](https://github.com/bhauman/lein-figwheel). The api\ndelivery needs to be running in order to use the web delivery.\n\nI'm not a lein wizard so I wasn't able to figure out how to run both in a single command (at least in a sane way) - so in order to demo the web app first start the api server in a terminal session:\n\n```\n$ lein api\n```\n\nThen in another terminal session start the web app:\n\n```\n$ lein web\n```\n\nYou should then we able to visit `http://localhost:3449` to see the re-frame app in action.\n\n\n## Testing\n\nThis application leverages a mixture of traditional unit tests and generative testing via `clojure.test.check`. It's\npretty dern cool, so check out the test suite. Unit tests and generative tests can be run at once via:\n\n```\n$ lein test\n```\n\n\n## Retrospect\n\nI'm not sure if this approach is a purist approach to the clean architecture. Everything is very use case driven, and the architecture *should* make it clear what the intent of the application is. The concept of input and output ports is handled (cleanly in my opinion) by [core.async](https://github.com/clojure/core.async) channels. Dependency injection is handled (again cleanly in my opinion) by [mount](https://github.com/tolitius/mount).\n\nI don't have much experience building applications in Clojure(Script) - but I have leveraged similar concepts in larger apps using different languages. While a todo app can only teach so much - I was just floored by the elegance of Clojure(Script) in building a use case centered app with different deliveries.\n\n### Basic Architecture\n\nThe architecture lays out like so:\n\n```\nresources/ -- static assets leveraged by web delivery\nsrc/\n  todos/\n    core/ -- contains use cases, entities, and some simple conventions for messaging\n      action/\n      entity/\n      use_case/\n        create_todo/\n\tdelete_todo/\n\tlist_todos/\n\tupdate_todo/\n    delivery/\n      api/ -- a simple restful api\n      cli/ -- a command-line interface to the app\n      web/ -- a re-frame application\n    storage/\n      todo/ -- protocol implementations for persisting todos\n```\n\n### On core.async\n\nThe concept of inputs and outputs in the clean architecture just seemed to make sense as core.async channels. While this made sense to me - I'm no expert and I'm not sure I fully understand the implications of this choice. I do know that having a use case depend on channels opens the door for tons of flexibility - i.e (one-to-many channels, controlling buffers, etc..). The api is also fairly easy to use.\n\nThe deliveries I created usually leveraged a put followed by a blocking take - mostly out of necessity (blocking in the cli for example). An async take would be pretty slick for streaming interfaces.\n\n### On clojure.spec\n\nclojure.spec is amazing. I'm likely not leveraging it to the best of it's ability. The cool things I saw were:\n\n* Generative testing via test.check. My functions are pretty thoroughly tested by a variety of inputs.\n* Validation. The api and web deliveries use specs to validate inputs and outputs. This is especially cool\nin the re-frame app as it checks the shape of app state after every mutation. This turned up all kinds of errors\nduring development.\n* Documentation. Specs do a great job of documenting intent\n\nSome things that I struggled with a bit - but might have the beginning of understanding:\n\n* Generators. Making custom generators took a bit of legwork to understand and employ\n* Convention - still not sure if specs make sense in the same file or a different one from the code they are describing. I ultimately settled on different files. It creates a little bloat - but makes sense and they are easy to import when composing other specs.\n\n### On ClojureScript\n\nThe web delivery leverages [re-frame](https://github.com/Day8/re-frame). This choice was largely due to my interest\nin the framework. I spend a good chunk of my day-to-day writing React/Redux apps using ES6/Next.\n\nI personally prefer ClojureScript over vanilla JavaScript. The tooling, the standard library, persistent collections out of the box.. You can just do more with less. [lein-figwheel](https://github.com/bhauman/lein-figwheel) solves a lot of the problems that webpack does, and in some ways I find it easier to use.\n\nThe web delivery may fall out of the clean architecture camp because it did not actually use any of the use cases. While it is structured by use cases - actually leveraging the use case namespaces felt awkward. Re-frame solves a lot of the input/output problems itself.\n\n**What I did get to re-use:** was my specs and core application code - thanks to the wonder of reader conditionals and .cljc files. This was nearly effortless, and it just blows my mind. Writing Clojure that works on the server and the client. Wow.\n\n### On code organization\n\nNot sure where I landed on this. The current code base is one repo supporting all deliveries. That means it is a mixture of `.clj`, `.cljs`, and `.cljc` files. At the time of this writing - [documentation](https://github.com/clojure/clojurescript/wiki/Using-cljc#general-considerations) on the subject isn't terribly clear on convention. The points made in favor of separate `src/clj`, `src/cljs`, and `src/cljc` directories are pretty compelling, and I think I would opt for this approach in the next app I build.\n\nThe benefit of one repo with many deliveries is still unclear to me. It wasn't terribly difficult, but I didn't do any of the work of deploying all deliveries. I'm not sure on the nuances of `lein` when it comes to building multiple jars with different requirements. Something to learn still! Basically comes down to the question of one `project.clj` or multiple?\n\nThe [lein-parent](https://github.com/achin/lein-parent) plugin seems like it offers some conventions for managing dependencies and multiple projects in a mono repo context.\n\n\n## Todos (snicker)\n\nThe problem space of todos provides a finite amount of interest for me. I left some things out, and there are some\nother things that I think would be cool. I may or may not ever get to any of these.\n\n* A re-natal delivery. It would be cool to see a delivery for a native mobile app\n* Error handling in the web delivery. Like a good \"RUSH TO MARKET APP\" the web delivery just assumes all http requests will always succeed.\n* Document more of this\n\n\n## Feedback / Pull Requests\n\nI would welcome any pull requests or general feedback on any approach taken here. I'm not sure I ever have or ever will fully grasp what a computer is, so I always welcome people telling me how to do things better :)\n\n\n## License\n\nCopyright 2017-2018, Brian Scaturro.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this code except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianium%2Fclean-todos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrianium%2Fclean-todos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrianium%2Fclean-todos/lists"}