
Writing a Microservice in Rust - blacksmythe
http://www.goldsborough.me/rust/web/tutorial/2018/01/20/17-01-11-writing_a_microservice_in_rust/
======
zbentley
Minor 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
_Const 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=6816dc9a03aca1e779f08443224...](https://play.rust-
lang.org/?gist=6816dc9a03aca1e779f08443224bee18&version=stable)

The downside of interior mutability is that borrowing rules are enforced at
run-time rather than compile-time.

~~~
pdpi
C++ also has the `mutable` as a way to achieve something akin to internal
mutability

------
Animats
_With 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
Thank you for the explanation, I never understood this. I wonder how many
people do though (is everyone on the same page about this, usually?).

~~~
steveklabnik
A 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
> 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.

~~~
steveklabnik
I see them as different axes. A service can be small, and it can be the only
service. SOA means your larger service is comprised of smaller services, but
says nothing about the size of those services.

An URL shortener is a micro service without SOA. A huge web application with
four services that are half a million LOC each is SOA, but not micro services.

I wonder what Martin Folwer has to say...

~~~
stronglikedan
> How big is a microservice?[0]

> Although “microservice” has become a popular name for this architectural
> style, its name does lead to an unfortunate focus on the size of service,
> and arguments about what constitutes “micro”. In our conversations with
> microservice practitioners, we see a range of sizes of services. The largest
> sizes reported follow Amazon's notion of the Two Pizza Team (i.e. the whole
> team can be fed by two pizzas), meaning no more than a dozen people. On the
> smaller size scale we've seen setups where a team of half-a-dozen would
> support half-a-dozen services.

> This leads to the question of whether there are sufficiently large
> differences within this size range that the service-per-dozen-people and
> service-per-person sizes shouldn't be lumped under one microservices label.
> At the moment we think it's better to group them together, but it's
> certainly possible that we'll change our mind as we explore this style
> further.

[0][https://martinfowler.com/articles/microservices.html](https://martinfowler.com/articles/microservices.html)

~~~
steveklabnik
Thanks! I was on mobile so it was a bit awkward. I'll have to revise my
understanding :)

~~~
pdpi
For what it’s worth, I much prefer your version. It emphasises the
similarities between “empterprisey” SOA and the “cool hip” Microservice, while
also highlighting one of the bigger differences.

People get too caught up in “how small is micro”, which is just silly
extremism.

------
allan_s
I'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
The 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.

------
grumpydba
Strikes 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
Why? 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, ...

~~~
akra
How come on every thread showing an example of how to do something in Rust
there's a staunch C++ defender thinking the whole world should be using their
language? C++ is a complex beast that due to its age has quite a number of
disadvantages and no two codebases I've seen are the same - a measure of a
language is its simplicity and C++ subjectively isn't thus. If you like C++
that's fine; doesn't mean you can't do it in Rust as well.

I think it's good that other languages are evolving that are safer, make it
easier to do concurrency, have an idiomatic style and still are performant
whilst removing legacy cruft. It's the natural evolution of things. Every C++
team I've been on debates every feature usage (e.g. auto/lambdas/etc) where
one team's good code is another's bad idea due to sheer amount of legacy
features vs new ways, what language constructs are good vs completely bad
ideas (everyone has completely different opinions), how should I import
libraries, what build tool to use, etc.

~~~
ovao
The commenter to which you’re replying is arguing that the cognitive overhead
in developing a microservice _even in C++_ isn’t so bad compared to, say, Go.

It doesn’t seem to be an advertisement for C++ or a condemnation of Rust.

~~~
akra
> Why? Even writing micro service in C++ is dead simple these days

Apologies to the commenter then. I misread that to be a "why bother?" to the
original blog post and do this instead. All tech has its pros and cons and
trying other things can be good too.

------
ilurkedhere
That does seem a bit painful to me. Maybe it will get better with some Rust
macro DSL though...

~~~
grayrest
> 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
That 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.

~~~
HumanDrivenDev
Disclaimer: I'm not in the 'rust community' (nor would I want to be!). I'm not
an evangelist for it, and I'm not very good at it. I like the language though.
Anyway...

I really think people need to get over syntax. I don't find rust particularly
good looking either, but I get over that because I like the semantics and it
sits in a nice place in the ecosystem of programming languages. Likewise i
don't like how C# uses PascalCase everywhere, or puts opening braces on its
own line - yet when I program in C#, I adhere to those conventions.

Syntax is incredibly subjective, and also superficial. If you think the
semantics of a language suck or are not a fit for what you are doing - that's
a reasonable conversation. But you really shouldn't limit your language choice
based on non-alphanumeric characters or whatever your objection is.

~~~
2trill2spill
> Syntax is incredibly subjective, and also superficial. If you think the
> semantics of a language suck or are not a fit for what you are doing -
> that's a reasonable conversation. But you really shouldn't limit your
> language choice based on non-alphanumeric characters or whatever your
> objection is.

I like the semantics of Rust, I actually use Rust, and plan on continuing to
do so. I still hate the syntax and module system though.

------
MrBuddyCasino
I 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
Why Kotlin but not Java?

~~~
Ygg2
\- 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=...](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=json))

~~~
openasocket
Kotlin 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.

------
Lerc
I 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
Is it public? Can I take a look?

~~~
Lerc
There'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
Had 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
Hey! 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?

~~~
kbenson
So... did you use HTTP because the client side required it or it made the
client side much easier, or was that just what came to mind? Because this
seems to be _exactly_ the type of thing I would generally use the default
OpenSSH installaction on the box for, pre-shared keys, and possibly even
setting a specific shell on the specified public key on the user side on the
server to prevent random shell access.

There's some really interesting advanced features of OpenSSH that most people
will never have need for, but you can come up with some really interesting
solutions. For example, you could also use a single remote account that allows
SSH access and has a separate public key for each user that sets an
environment var for the target desires user, and restrict the command run to
sudo with that environment variable defining to run as that specific user, and
make sure sudo is configured for the users allowed.

A microservice isn't a bad idea, it's just interesting how many ways there
usually are to accomplish what seems like odd, specific custom workflow in
most UNIX environments.

~~~
Lerc
Client side was all browser.
[https://github.com/Lerc/notanos](https://github.com/Lerc/notanos) also
incomplete. I go back and add things to it from time to time. It's my long
term toy project.

------
Promarged
Shouldn'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).

------
hinkley
The 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.

------
Shoothe
Excellent guide, something that I'll need in near future.

I wonder why the author chose hyper instead of iron though...

~~~
beckler
I'm pretty sure Iron is no longer maintained.

~~~
carlosdp
Yea unfortunately Iron isn't actively maintained anymore.

------
always_good
Diesel 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`.

~~~
anentropic
I'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?)

~~~
Sean1708
Diesel 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.

