Federated blogging application, thanks to ActivityPub https://joinplu.me
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

101 lines
5.5 KiB

  1. # Refactoring
  2. This is document describes the Design Goals and Refactoring Strategies of our ongoing efforts to improve the architecture, and with it the stability and performance of Plume.
  3. ## Current Issues
  4. Let's look at the current architecture's problems, and the attempts we've made at resolving them.
  5. ### PlumeRocket
  6. This data structure was [introduced by yours truly](https://github.com/Plume-org/Plume/pull/462) with the intent to please Clippy, reduce the number of arguments passed around (the main thing Clippy complained about).
  7. We also tried reduce the amount of bytes being passed around, by using references.
  8. At the time, this seemed like a good idea.
  9. Right now, it's the main source of our problems.
  10. This `struct` bundles `DbConn`, which makes it difficult migrate Plume to `async` Rocket:
  11. Passing around a giant `struct` as `ref` in `async` world, means that different owners are waiting for it to be `.await`ed, so they can access them.
  12. This makes working with such a `struct` very unwieldy, if not impossible sometimes.
  13. ### DbConn, Searcher and Post
  14. `DbConn` and `Searcher` are deeply bundled via `Post`.
  15. `Searcher` is called every time a `Post` is added/updated/deleted.
  16. It needs access to `DbConn` to fill the data that `Post` does not have.
  17. While removing one or the other from `PlumeRocket` is possible, complications still arise with `AsObject`:
  18. Posts's `AsObject` APIs are called every time a Post is added/updated/deleted.
  19. It builds on `PlumeRocket` as main `Context`, and so we'd have to touch every API if we split either `DbConn` or `Searcher` out of `PlumeRocket`
  20. ## Solution Attempts and their Problems
  21. in the past couple of weeks, we've made the following attepts to at least partially dissolve `PlumeRocket`
  22. - [plume-model: refactor Searcher to have its own DbPool](https://git.joinplu.me/Plume/Plume/pulls/809)
  23. - [WIP: Experiment: extract Searcher into an Actor](https://git.joinplu.me/Plume/Plume/pulls/807)
  24. - [extract DbConn from PlumeRocket](https://git.joinplu.me/Plume/Plume/pulls/805)
  25. As soon as we attempted to delete out one of the members from `PlumeRocket`, compiles would fail all over the place, meaning we'd have to touch almost every single function's *signature* that uses `PlumeRocket`.
  26. This then means we'd have to touch every single function that in turn use those functions!
  27. That is a lot of broken code, before we've even started refactoring.
  28. ## Strategy
  29. Despite ambitions to use an [Actor System (Riker)](https://riker.rs/), it is not magnitude of the ambitions, but the size of the steps we've taken.
  30. So, given past failures we devise a strategy.
  31. We want to replace each of the systems in `PlumeRocket` with an `Actor`, accessed by a single reference to the `ActorSystem`.
  32. This way we don't have to touch every single function's parameters that `PlumeRocket` flows thru, while the code is still in motion.
  33. ### Actors
  34. in [#807](https://git.joinplu.me/Plume/Plume/pulls/807), we've already made our first attempt at extracting `Searcher` into a Riker `Actor`.
  35. Here, just like in [#809](https://git.joinplu.me/Plume/Plume/pulls/809), we've already given `Searcher` its own `DbPool`.
  36. Why?
  37. ### DbPool instead of DbConn
  38. In our previous attempts at this code, we've realized that `DbPool`, being wrapped in an `Arc` is very cheap to `.clone()`.
  39. This means that every `Actor` that needs a `DbConn`, could get its own `DbPool`.
  40. We also realized that `DbPool` acts like an `Actor` in its own right:
  41. - `DbPool` has a `.get()` message
  42. - when that message is sent, it responds with a `DbConn`
  43. - if the pool is exhausted, it does not!
  44. Thus, we conclude:
  45. We want to `.clone()` a `DbPool` for every Actor that we extract from `PlumeRocket` that needs it.
  46. ### Workers
  47. In [#799](https://git.joinplu.me/Plume/Plume/issues/799#issuecomment-4402), we've identified the following `workers`:
  48. > Here is the list of things the worker is used for:
  49. >
  50. > - sending activities to other instances (just needs the activity and a list of recipients)
  51. > - rotating user keypair when they delete a post (after 10 minutes), which requires the DB
  52. > - fetching posts for a blog/users, either because it is new or because it has not been updated in 24 hours (depends on the DB too, and on the searcher)
  53. For the first type of worker, we'll have to make *repeated* network requests.
  54. There's a [Riker issue](https://github.com/riker-rs/riker/issues/130) asking how to best do that (with an answer.)
  55. The two workers that need access to the database, should get their own `DbPool`.
  56. Being part of the same `ActorSystem`, the last type of worker will be able to access `Searcher` thru messages.
  57. ### Request Path vs DbConn vs async
  58. For the Rocket Request path, we want to wrap `DbPool` in an `Actor` of its own:
  59. Then, in the `RequestGuard` we'd [ask](https://riker.rs/patterns/#ask) for a `DbConn`.
  60. This brings us on-par with Rocket's current [`#[database]`](https://github.com/SergioBenitez/Rocket/pull/1375) approach, that puts database connection access into `spawn_blocking()`.
  61. However, unlike `#[database]`, Riker's `ask()` responds with a Future, which would mean that in `async` functions we could simply `.await` it!
  62. ## The long road
  63. Once we've refactored the main systems in `PlumeRocket` into their own Actors, and once we're down to only the `ActorSystem` being the main component of `PlumeRocket`, we can finally shed the husk.
  64. That means that we *do* go and touch all the functions that take `PlumeRocket` and only give them access to the things they need.
  65. This is also a good chance to look at functions that are too long, or are doing to much, and mark them for refactoring.