13 ms·
Writing a Microservice in Rust
- Shoothe 9y agoExcellent guide, something that I'll need in near future. I wonder why the author chose hyper instead of iron though...
- allengeorge 9y agoOr rocket/Gotham. Personally, I preferred working with rocket far more than iron, and that despite the fact that rocket is nightly-only.
- Spartan-S63 9y agoThe plan is for Rocket to run on stable by the end of 2018. v0.4 is getting closer and the two biggest features remaining to be implemented for it are connection pooling (which I have an open PR that's being worked on) and CSRF support.
- kibwen 9y agoCan you cite this claim? I've taken a look at the list of unstable features that Rocket uses, and at times it almost seems like a deliberate ploy to use every nightly feature imaginable. And the feature that it really does actually need for its API, procedural macros, isn't guaranteed to land this year AFAIK (of course, I'd love to be wrong).
- steveklabnik 9y agoSergio has said it on IRC multiple times. Procedrual macros are not only landing this year, but in the first half of the year. See the roadmap.
- kibwen 9y agoI've seen that macros 2.0 are landing this year, but it's unclear to me whether "macros 2.0" only refers to `macro!` or also to procedural macros.
- steveklabnik 9y agoMacros 2.0 means both Macros By Example 2.0 and Procedrual Macros 2.0. :confetti_ball:
- beckler 9y agoI'm pretty sure Iron is no longer maintained.
- carlosdp 9y agoYea unfortunately Iron isn't actively maintained anymore.
- azdle 9y ago> Nevertheless, I believe taking this route and going slightly lower level with Hyper gave you some nice insights into how you can leverage Rust to write a safe and performant webservice. Using hyper directly definitely gives you a better idea how everything fits together and it is much more helpful for understanding how Futures work.
- wawf233epoiwe 9y agoRead the post; they explain the choice
- grumpydba 9y agoStrikes me as having quite a big cognitive overhead. I wouldn't try using this in unless being cornered by performance considerations or wanting a pure rust software architecture.
- majidazimi 9y agoWhy? Even writing micro service in C++ is dead simple these days: 1. write the service specification in Thrift. 2. Generate the server and client stub with the framework. 3. Write the service functions. 4. Most probably you'd need to include some database/cache libraries. Poco project does a fairly good job. 5. Add some more logging and metric libraries. 6. General tools/libs are available in poco and folly. 7. If you are messing with REST, Microsoft Rest SDK and Facebook Proxygen is already providing a very solid foundation. High performance servers are already written. You just need to fill the call backs and configure the thread pools. Why do you consider this more difficult than doing the same in Go? You just need some intermediate C++ knowledge for above tasks. No need to touch template programming, manually managing threads, ...
- iTokio 9y agoBecause Go has built in primitives to make micro services. The cleanest asynchronous io model that I have ever seen. Simple serialization by default. Did I mention the blazing fast compile times? Just that would make it way more “faster” to build your service in Go :) And above all the language is simple with robust idioms. Okay, I admit it is somewhat too simple, cough generics. And you won’t get C like performances for sure.
- woah 9y agoIMO, the goroutine model has the same synchronization challenges as multithreaded programming. For network code that is io bound, I find async/await much cleaner.
- jrs95 9y agoIt's not that bad with channels. I suppose you don't have to worry about synchronization issues as much with async/await on Node since that's a single threaded environment, but async/await with something like C# isn't any less complicated in that regard that goroutines and channels. Generally Go code isn't written like typical multithreaded code with synchronization unless you're really trying to optimize something.
- ilurkedhere 9y agoThat does seem a bit painful to me. Maybe it will get better with some Rust macro DSL though...
- lewisinc 9y agoWell yeah, that was the point: "While there exist a number of high-level, Flask or Django like frameworks that abstract away most of the fun about this, we will opt for using the slightly lower-level hyper library to handle HTTP..." You should check out Rocket or Actix-Web or Gotham for something a bit more higher-level. Also know that it's Rust we're talking about - it's an expressive languages, but it's also a systems language, if you want something you can toss in a breakpoint and start introspecting or experimenting you'll probably want Ruby or Elixir.
- grayrest 9y ago> That does seem a bit painful to me. It's because it's written using a low level library. Here's the first two sections (the http handling part) in Rocket: #![feature(plugin, custom_derive)] #![plugin(rocket_codegen)] extern crate rocket; use rocket::request::Form; #[derive(FromForm, Debug)] struct NewMessage { username: String, message: String, } #[derive(FromForm, Debug)] struct TimeRange { before: Option<i64>, after: Option<i64>, } #[post("/", data="<message>")] fn message_create(message: Form<NewMessage>) -> String { format!("{:?}", message) } #[get("/?<times>")] fn message_query(times: TimeRange) -> String { format!("{:?}", times) } fn main() { rocket::ignite() .mount("/", routes![message_create, message_query]) .launch(); }
- 2trill2spill 9y agoThat code looks awful, sorry but rust syntax is horrible. I want to like rust but after a couple months of trying I hate the syntax and the over complicated nature of the language.
- always_good 9y ago
- zbentley 9y agoMinor nitpick: > Immutability (const) by default The author clearly knows this, but others may not, so: immutability and "const" aren't the same thing. Const typically prevents name rebinding (as the type system permits). Immutability is the presumption that the whole data structure can't be mutated by default (though there are usually ways to mutate cheaply--without bulk copying, that is--provided by most immutable data APIs). Const works like this: not_const int[] foo = [123, 456] const int[] bar = [456,789] foo = [1,2,3] // no problem; rebinding is allowed if it meets the 'int' type constraint. bar = [1,2,3] // compile error! Immutability, as typically defined (there's a bit of a semantic debate here sometimes) prevents changes to the contents of complex data structures: mut int[] foo = [123, 456] not_mut int[] bar = [456,789] foo[0] = 789 // no problem; mutation is allowed so long as it's with valid types. bar[0] = 123 // compile error! Edit: missed a space.
- danieldk 9y agoConst typically prevents name rebinding (as the type system permits). In C++ you can only call `const` methods through a const binding. `const` methods mark `this` const, so such methods cannot modify member variables. (Minus all escape hatches, such as `const_cast`.) The author clearly knows this, [...] Immutability is the presumption that the whole data structure can't be mutated by default The situation in Rust is a bit more complex. Rust distinguishes between interior and exterior mutability. `let mut` bindings and `&mut` references exhibit exterior mutability - you can call methods that borrow `self` mutably (`&mut self`). Whereas `let` bindings and `&` references cannot. By the way, you can simply use moves to introduce exterior mutability for data that you own: let a = Vec::new(); // Not allowed: a.push(1); // Move the vector. let mut b = a; b.push(1); Even even if a data type uses an immutable binding or reference, it could still mutate that data structure if it has interior mutability (you just can't call methods that use self mutably). See the following small example, which uses an immutable binding to call a method that mutates the internal state of the struct. https://play.rust-lang.org/?gist=6816dc9a03aca1e779f08443224bee18&version=stable https://play.rust-lang.org/?gist=6816dc9a03aca1e779f08443224... The downside of interior mutability is that borrowing rules are enforced at run-time rather than compile-time.
- MrBuddyCasino 9y agoI made a very simple web service with Rocket (see https://github.com/MrBuddyCasino/alexa-auth https://github.com/MrBuddyCasino/alexa-auth), which is better but still - I‘d go for Kotlin or Go instead if performance requirements allow it.
- geezerjay 9y agoWhy Kotlin but not Java?
- Ygg2 9y ago- JVM is a good platform? Aka if you can think about it, it's probably been done in JVM and/or Java. - Java's GC is highly customizable? - It's one of top contenders in most web related benchmarks (see: https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=json https://www.techempower.com/benchmarks/#section=data-r15&hw=...)
- openasocket 9y agoKotlin runs on the JVM too, and easily interops with Java libraries and frameworks. So all three of your points apply equally to Kotlin as they do to Java.
- MrBuddyCasino 9y agoJava is fine, too. If I have the choice I prefer Kotlin these days. Same as if I have the money I go by taxi, not by bus.
- erpellan 9y agoKotlin is what Java would look like if it was invented today. Type inference, explicit non-nullabililty, first class functions, function types, extension methods, etc. Eveyone I know who's used Kotlin would not willingly go back to Java.
- leshow 9y agoAre you sure rocket is actually faster than the Go/JVM equivalent? I'm a big fan of Rust, and I don't deny that it's possible for it to be faster, but my understanding of where Rocket's async story is that it isn't there yet. You're also using reqwest::Client inside your handlers, which is synchronous. I'd just find it hard to believe that could beat an async JVM or Go implementation.
- allan_s 9y agoI'm waiting for the service to be a bit more consistent to blog about it, but we've started with a friend to write a webservice in rocket and it's been a total pleasure It may be useful to some people, as we've most of the feature you find in a "real life" service * database connection * schema creation * json handling * pagination * some endpoints are non-json * support for CORS * automated isolation test (i.e we test the service as a blackbox) * generation of a minimal bin-only docker image that with travis-ci * self-contained dev environment (vagrant up and you're good to go) https://github.com/allan-simon/sentence-aligner-rust https://github.com/allan-simon/sentence-aligner-rust
- bluejekyll 9y agoThe only catch I've had with Rocket is that it relies on nightly. This can create issues with other libraries, and it is constantly requiring new versions of the nightly version. If you're going to use Rocket, I recommend pinning the Rocket version in a Cargo.lock, and setting a specific version of nightly with rustup override.
- always_good 9y agoDiesel is a synchronous library, so the post's database functions make a blocking call and then return a future which, of course, doesn't make them asynchronous. They'd still block the event loop. You could fix this by passing a `&futures_cpupool::CpuPool` into your database functions and wrapping the post's function bodies in `pool.spawn_fn(|| { ... })` so that they execute on a different thread and return a future of their result. To have a global IO pool that you pass into fns, you can store it in the Service struct: struct Microservice { io_pool: CpuPool } Now you can access it in your service's handler (the call() fn) with `self.io_pool`.
- karavelov 9y agoOr use pg_async[1] that integrates with Futures/Tokio/Hyper. [1] https://crates.io/crates/pg_async https://crates.io/crates/pg_async
- anentropic 9y agoI've never touched Rust or Diesel... but what is the point of a synchronous library that makes blocking calls _and returns futures_? (I mean - why futures, if the call was blocking and presumably have a real result to return?)
- rejschaap 9y agoWild guess here, but it might make it compatible with frameworks or middleware that expects futures.
- Sean1708 9y agoDiesel doesn't return futures, the author of the blog post returned a future and the person that you commented on is pointing out that that's pointless because Diesel is synchronous.
- Lerc 9y agoI haver been looking at Rust as a replacement for a c microserver I made, I have a thing that listens as root and serves as a user dictated by their id. so it's as root: on connect fork: forked process: read request to determine ID drop privileges to UID and GID of ID process request Because of the root component I want it to do as little as possible while being root, which c is good for. but it's also probably riddled with holes, only some of which I am aware.
- muricula 9y agoIs it public? Can I take a look?
- Lerc 9y agoThere's a version of some vintage at https://github.com/Lerc/userserv https://github.com/Lerc/userserv (it's terrible, don't judge me) It has node bits there but they aren't necessary for the basic static serving. If it gets a WebSocket connection it punts it on to node.
- symtos 9y agoHad a quick glance and your code is littered with unchecked function calls and potential overflows. Also: Cookie:../../../<filename> Where <filename> is a file starting with a value that's interpreted as a valid uid by atoi(). You're saved by a NULL pointer deref when the unchecked getpwuid() fails if the resulting uid is >0 but invalid (unless you're running it on a system where NULL is mapped to readable memory).
- Lerc 9y agoHey! I said don't judge me :-) The reality is that I only wrote as much as I needed to go back to working on the project I needed it for. It works for the 'Everything is fine' case, which is what I needed to go back to developing the client side. Even a hint of malicious intent could probably bring it to it's knees. But therein lies the rub. Is it worth hardening it or should I just go to Rust where most of those things just won't pop up?
- Animats 9y agoWith microservice, I mean an application that speaks HTTP, accepts requests, speaks to a database, returns a response (possibly serving HTML), packaged up in a Docker container and ready to be dropped somewhere in the cloud. Huh? That's a CRUD app. A "microservice" is a service which is a component of a larger service. Usually a service run by the same organization. Many microservices don't speak HTTP; they use Google protocol buffers or something similarly efficient. Serving a Facebook page involves about a hundred microservices, and they don't talk HTTP to each other.
- meuk 9y agoThank you for the explanation, I never understood this. I wonder how many people do though (is everyone on the same page about this, usually?).
- coldtea 9y agoNo, and it's not even a very useful distinction to make. Just keep that a microservice doesn't have to speak HTTP (or be in Docker for that matter).
- steveklabnik 9y agoA microservice is about the size of a service, not about the underlying technology. This service has few endpoints, and so is a microservice. This also isn't a CRUD app, as it has no delete, for example. It only has the CR of CRUD.
- nostalgeek 9y ago> A microservice is about the size of a service Is it? Isn't it a purely organizational paradigm where each teams are responsible for a specific service and clear API boundaries are defined between teams? rather than the "size" of an application? (which is hard to quantify, if my app does AI and needs 100,000 line of code for a single endpoint, is it still micro?). That's why I believe "micro" is an unfortunate qualifier what is essentially basic SOA.
- hinkley 9y agoThe amount of ad network traffic rolling off of this page is staggering. Made more amazing by the fact that only one ad shows on the entire page.
- Promarged 9y agoShouldn't RUST_LOG use "microservice_rs" instead of "microservice"? the "_rs" is a crate name. Another thing, in a make_post_response stub function I think it's better to use StatusCode::NotImplemented instead of NotFound (when trying this code I thought the match did not work correctly as everything returned NotFound).