Overview

Alright, what do we want?

API

Something that’s fairly important is to provide versioning to the API. Going by semantic versioning, only major version bumps can cause breaking changes; hence, only the major version number is needed to access a path.

The root URL can be https://eldred.fr/fibrs/v1/, for example.

I will want 3 endpoints:

  • /status, for querying the API’s status;
  • /fib, to compute fib(n) (first requirement);
  • /inv, to compute the inverse to the closest Fibonacci number (second requirement).

Also yes, I like my URLs clean and short.

As for the operations on those endpoints, all the data is read-only anyway, so we only need to care about GET.

Server

Single-responsibility in action!

We will want a front-end, that deals with everything related to processing incoming requests, and a back-end, that actually crunches the numbers.

Interestingly, with the way Rust is designed, a “crate” can contain both programs and/or libraries! It is thus wise to put the back-end in a “library” crate, and the front-end in a “binary” crate. This would allow the back-end to be used by anything that just wants Fibonacci’s, and possibly having different front-ends share back-end improvements.

As for the back-end itself, it will need to expose the functionality needed by the API; the front-end should do little more than unpacking HTTP requests: perhaps translating the data a bit, but no computations!

The back-end will contain the storage/cache… and that will probably be it.

Aside

Oh, and, I had an idea for a possible improvement later: to improve memory usage, the cache could be made sparse!

For example, storing only every other pair of numbers. That way, the two numbers after can be fairly quickly re-computed, trading a bit of processing time for half the memory usage! I would be interesting to provide several caches, each implementing a different caching strategy, but with a common interface.

Oh, and by the way, it’s important to cache at least two consecutive numbers. If only storing every other result, say, fib(2n), then computing e.g. fib(7) would find fib(6) cached, but would need to compute fib(5), and so on… and we can see that the cache isn’t actually doing much. Storing two consecutive numbers means the cache will always stop the computation early!

To make the cache more sparse, storing e.g. 4 numbers then leaving 4 holes is intuitively fairly inefficient, since the first 2 numbers would not speed up any following numbers, whereas the latter 2 numbers would “cache for 4”, creating an imbalance. It would probably be more efficient to increase the amount of “holes” between cached entries.

But I digress; whether to use these optimizations is something that is better discussed later, including after gathering some usage data. If consumers have a bias in how they request the API, other strategies for how to make the cache sparse may be more efficient, such as a “freshness”-based cache.

Still something interesting to keep in mind for later, and for designing too: we will probably want a “cache” to be an interface, rather than a single class, so as to allow using any of these strategies interchangeably!

↑