Building a Micro-Service in Rust and Python

Recently I needed to develop a small microservice to continuously collect, process and alert on a stream of information. Ideally it would require very little memory, be CPU efficient and have consistent performance.

I'd always been interested in Rust and have written my fair share of 'toy' apps and this seemed like a great way to learn more with a 'realistic' project. Still, I wasn't quite ready to make the leap on this job but I did decide to take the time to explore the problem space with something similar; something that would be small, include more concerns than most tutorials and examples, and yet be easy to understand.

This exercise would require some time but be a great learning experience. Also, as a machine learning engineer most of my work is in Python (which I love) so I thought that writing the same service in both languages would be a beneficial comparison. BTW, the main thing I love about Python is the machine learning ecosystem and the ease with which you can develop small applications. And the things that I feel could be improved (among others) include, more type hint support, better error handling strategy, exhaustive case/switch matching (there is none) and the ability to create a standalone executable (Docker is not a universal solution).

The Goal/Requirements

Recently, I found out about financialmodelingprep.com where you can get financial data from a public unrestricted API. So after some thought, I settled on a service that would serve stock price data that it retrieved and enhanced from that service.

Though currently there is no rate limit on that API it is easy to imagine a scenario where it would be useful to:

  • act as a cache for an expensive/slow service
  • act as a proxy for authorization purposes
  • abstract and standardize one or more APIs
  • enrich or combine the data itself in useful ways

For microservices I would also consider gRPC but to keep things simple here we'll use regular HTTP and create the following simple routes:

  • / - basic info to make sure the service is up
  • /instruments/{symbol} - get price quotes for a particular symbol/asset
  • /instruments/{symbol}/stats - gets statistical (enriched) information for that asset

Both of the instrument routes also accept optional start date, end date and duration parameters to specify the date range for quotes and summary stats.

The Code

Both the Python and Rust versions are available on my Github. I tried to comment the code and it should be fairly easy to follow. Note though, that I decided not to invest/spend the time to properly handle errors and testing as this was still just an exercise (a common excuse I know but we all have limited time) and requirements were simply examples. Even in the simplest applications there are lots of subtle issues around requirements that a project manager or owner would have to decide.

For python I used FastAPI which is a great way to build API focused services. You could do something similar with Flask but FastAPI is asynchronous, type based, and provides a nice swagger endpoint with documentation for no additional effort. Persistence is handled by SQLAlchemy and PostgreSQL

In the Rust version I went with Actix for the web framework, Diesel for the ORM, Chrono for date utilities and Serde for JSON serialization and deserialization. You may have to add and configure more libraries to the base Rust system (Python comes batteries included) but you can choose exactly what you want/need.

Conclusions

I have to admit that the Rust version took (me) a lot longer to develop. A big part of that is that I was way more familiar with Python than with Rust: So no surprise there.

Having said that, the Rust version exposed some initial sloppy thinking (particularly around dates and times and time zones) that made the service more resilient (even though it was a pain at the time). Plus, I feel that if business requirements were to change in the future it would be an easier codebase to update. Python has typehints and mypy which are great, I use them whenever I can, but they're not a substitute for an integrated type system, result type, option type, etc.

In conclusion:

  • I do a lot of machine learning in Python and to serve those models it makes sense to use a Python micro-service. Particularly since, almost by definition, the code base and interface can be kept small and focused. I highly recommend FastAPI if it might fit your use case.

  • Anything that is not machine learning specific, where I might care about performance and correctness, or that may grow over time I will try to develp in Rust.

Want to get notified of new articles and projects?

Get an occasional email with AI/ML and project info.

© 2020 E-String Technologies, Inc. | Privacy