Slide 1

Slide 1 text

2019/10/26 “Making an opinionated Web framework” 1 Making an opinionated Web framework Masaki Hara (Software Engineer at Wantedly) 2019/10/26 @ Rust.Tokyo 2019

Slide 2

Slide 2 text

2019/10/26 “Making an opinionated Web framework” 2 Myself • Software Engineer at Wantedly • Ruby on Rails and Golang • Wantedly People (Business card scanner & SNS) • Current interest: Rust • Former interests: formal methods, competitive programming, math, ...

Slide 3

Slide 3 text

2019/10/26 “Making an opinionated Web framework” 3 Part I: Before designing WAF • Background: my Rust interests • Background: my jobs • Single-feature servers • Domain servers • Why WAF now? • Our prototype Briefly describes why I started building WAF.

Slide 4

Slide 4 text

2019/10/26 “Making an opinionated Web framework” 4 Part II: Implementation • Example app: “conduit” • Async ecosystems • Structure • Nightly • Derive macros • Basic HTTP Tools • Diesel async wrapper • Overall progress Shows how our (partial) implementation is going.

Slide 5

Slide 5 text

2019/10/26 “Making an opinionated Web framework” 5 Part III: Design Questions • Schema-first development • Error handling • Configuration and DI • Crate splitting • Other questions raised Enumerates questions brought up during WAF design and our preliminary decisions.

Slide 6

Slide 6 text

2019/10/26 “Making an opinionated Web framework” 6 Part I: Before designing WAF

Slide 7

Slide 7 text

2019/10/26 “Making an opinionated Web framework” 7 Background: My Rust Interests • Language specs, features, and tricks Part I > Background

Slide 8

Slide 8 text

2019/10/26 “Making an opinionated Web framework” 8 Background: My Jobs • Backend: microservices written mostly in Rails and Golang • Performance is important but not critical Part I > Background

Slide 9

Slide 9 text

2019/10/26 “Making an opinionated Web framework” 9 Background: My Jobs • How can Rust perform better in our purpose? Part I > Background

Slide 10

Slide 10 text

2019/10/26 “Making an opinionated Web framework” 10 Single-feature servers • Very simple tasks that can be extracted to a microservice “furiganer” “原 将己” “はら まさき” Part I > Single-feature servers

Slide 11

Slide 11 text

2019/10/26 “Making an opinionated Web framework” 11 Single-feature servers • We already have one in Rust “refine-image-rust” S3 A bit context here: our service allows user to scan business cards. Part I > Single-feature servers

Slide 12

Slide 12 text

2019/10/26 “Making an opinionated Web framework” 12 Domain servers • A server with a single interest and multiple operations “users-rails” Register Get user info List users Update user info Part I > Domain servers

Slide 13

Slide 13 text

2019/10/26 “Making an opinionated Web framework” 13 A pair of domain servers • Servers to deliver a timeline of SNS contents “yashima-updates” “yashima-updates- contents” Each content A timeline A bit context here: our service has SNS feature based on the users’ business cards. Part I > Domain servers

Slide 14

Slide 14 text

2019/10/26 “Making an opinionated Web framework” 14 A pair of domain servers • Servers to deliver a timeline of SNS contents “yashima-updates” “yashima-updates- contents” Each content A timeline Fast simple delivery Easy implementation of complex business logics Part I > Domain servers

Slide 15

Slide 15 text

2019/10/26 “Making an opinionated Web framework” 15 A pair of domain servers • This split was well-designed and is working well. • However, there’s still a pain in the split; we often have business requirements which spans both servers. • What if there’s a language which is fast enough, scales well, and can handle complex business logics with reusable combinators…? Part I > Domain servers

Slide 16

Slide 16 text

2019/10/26 “Making an opinionated Web framework” 16 A pair of domain servers • This split was well-designed and is working well. • However, there’s still a pain in the split; we often have business requirements which spans both servers. • What is there’s a language which is fast enough, scales well, and can handle complex business logics with reusable combinators…? There is, but we need the ecosystem Part I > Domain servers

Slide 17

Slide 17 text

2019/10/26 “Making an opinionated Web framework” 17 Then, why WAF now? •…because we have the largest turning point in the Rust webserver ecosystem. Part I > Why WAF now?

Slide 18

Slide 18 text

2019/10/26 “Making an opinionated Web framework” 18 Quote from diesel async issue… • On 2018-01-13, @sgrif said… “Neither tokio nor futures have stable APIs at the moment” “It's virtually impossible to have anything that isn't 'static.” The lack of borrowing awaits has been blocking the whole async ecosystem! Part I > Why WAF now?

Slide 19

Slide 19 text

2019/10/26 “Making an opinionated Web framework” 19 We’ll experience a drastic development • async/await isn’t just a syntax sugar; this is the promising API for borrowing awaits, which didn’t exist before. • Many libraries can start designing their async API without sacrificing borrowing. • Competitions will be invoked by async/await release, and we may have “de facto standard” libraries for each genre. Part I > Why WAF now?

Slide 20

Slide 20 text

2019/10/26 “Making an opinionated Web framework” 20 Our prototype •See https://github.com/qnighy/nails •We’ll describe the progress in the next part Part I > Our prototype

Slide 21

Slide 21 text

2019/10/26 “Making an opinionated Web framework” 21 Part II: Implementation

Slide 22

Slide 22 text

2019/10/26 “Making an opinionated Web framework” 22 Example app: “conduit” • Same API, multiple implementations • Definitely useful for designing WAFs https://github.com/gothinkster/realworld Part II > Example app “conduit”

Slide 23

Slide 23 text

2019/10/26 “Making an opinionated Web framework” 23 Async ecosystems tokio • The battle-tested runtime and ecosystems rustasync + romio + juliex • New runtime and ecosystems made by the official working group Part II > Async ecosystems

Slide 24

Slide 24 text

2019/10/26 “Making an opinionated Web framework” 24 Async ecosystems tokio • The battle-tested runtime and ecosystems rustasync + romio + juliex • New runtime and ecosystems made by the official working group We once chose it (with some missing parts from tokio/hyper) Part II > Async ecosystems

Slide 25

Slide 25 text

2019/10/26 “Making an opinionated Web framework” 25 Rustasync fate • https://blog.rust-lang.org/2019/09/30/Async-await-hits- beta.html Part II > Async ecosystems

Slide 26

Slide 26 text

2019/10/26 “Making an opinionated Web framework” 26 Structure . lib nails nails_derive project_examples nails_realworld Cargo.toml Frameworks “Realworld” example Workspace manifest Part II > Structure

Slide 27

Slide 27 text

2019/10/26 “Making an opinionated Web framework” 27 Nightly for async/await • Pinned to the latest nightly with rustfmt, clippy, and rls. • Moved to 1.39.0 beta Part II > Nightly for async/await

Slide 28

Slide 28 text

2019/10/26 “Making an opinionated Web framework” 28 Derive macros • High priority because it’s tied to the core developer experience Part II > Derive macros

Slide 29

Slide 29 text

2019/10/26 “Making an opinionated Web framework” 29 Basic HTTP Tools These were necessary in the early stage of prototyping: • Routing • Parsing query strings • Parsing JSON bodies Part II > Basic HTTP Tools

Slide 30

Slide 30 text

2019/10/26 “Making an opinionated Web framework” 30 Diesel async wrapper • Diesel is a (supposedly most famous) type- safe query builder and ORM • Doesn’t support async yet • We’ll need the async wrapper eventually, but not for the prototyping Part II > Diesel async wrapper

Slide 31

Slide 31 text

2019/10/26 “Making an opinionated Web framework” 31 Overall progress • Not good ;( • ☑️ A derive macro for parsing requests • ☑️ Basic routing • ☑️ Error handling • ☑️ Async/await • ☐ Schema generation • ☐ Better ORM integration • ☐ Working webapp example • ☐ Middleware interface • ☐ Generators / scaffolding • ☐ Polish polish and polish… Part II > Overall progress

Slide 32

Slide 32 text

2019/10/26 “Making an opinionated Web framework” 32 Part III: Design Questions

Slide 33

Slide 33 text

2019/10/26 “Making an opinionated Web framework” 33 In this part… • As I said in the last page, our framework itself is totally under development. • However, we’re sure we already had good design questions and partial answers to these. • In this part, we focus on questions, rather than how I solved. Part III > In this part…

Slide 34

Slide 34 text

2019/10/26 “Making an opinionated Web framework” 34 Schema-first development • In a statically-typed language, we can give each endpoint a type. • We want to help clients (e.g. JS frontend, Android, iOS, …) build correct requests using these types. Part III > Schema-first

Slide 35

Slide 35 text

2019/10/26 “Making an opinionated Web framework” 35 Case I: gRPC and grpc-gateway • gRPC by itself is a schemaful solution, but it can also be transformed to a schemaful JSON sever using grpc-gateway. From https://github.com/grpc- ecosystem/grpc-gateway Part III > Schema-first

Slide 36

Slide 36 text

2019/10/26 “Making an opinionated Web framework” 36 Case I: gRPC and grpc-gateway “yashima-updates” “yashima-updates- contents” Each content A timeline This is grpc-gateway They interact using protobuf Part III > Schema-first

Slide 37

Slide 37 text

2019/10/26 “Making an opinionated Web framework” 37 Case I: gRPC and grpc-gateway • Protocol example Part III > Schema-first

Slide 38

Slide 38 text

2019/10/26 “Making an opinionated Web framework” 38 Case I: gRPC and grpc-gateway • Protocol example Part III > Schema-first

Slide 39

Slide 39 text

2019/10/26 “Making an opinionated Web framework” 39 Case I: gRPC and grpc-gateway • Generated codes Part III > Schema-first

Slide 40

Slide 40 text

2019/10/26 “Making an opinionated Web framework” 40 Case I: gRPC and grpc-gateway • Generated JSON schema (OpenAPI/Swagger) Part III > Schema-first

Slide 41

Slide 41 text

2019/10/26 “Making an opinionated Web framework” 41 Case I: gRPC and grpc-gateway • There are several implementations in Rust too https://github.com/hyperium/tonic https://github.com/stepancheg/grpc-rust https://github.com/tikv/grpc-rs Part III > Schema-first

Slide 42

Slide 42 text

2019/10/26 “Making an opinionated Web framework” 42 Case II: GraphQL • See this slide from my colleague ☺ https://speakerdeck.com/chloe463/graphqlsabafalsesukimahuasutokai-fa-woban-nian-jing-te Part III > Schema-first

Slide 43

Slide 43 text

2019/10/26 “Making an opinionated Web framework” 43 Case II: GraphQL • Famous Rust implementation https://github.com/graphql-rust/juniper Part III > Schema-first

Slide 44

Slide 44 text

2019/10/26 “Making an opinionated Web framework” 44 Case III: NestJS / FastAPI • Generate schemas from type annotations! Taken from https://fastapi.tiangolo.com/tutorial/body/ Part III > Schema-first

Slide 45

Slide 45 text

2019/10/26 “Making an opinionated Web framework” 45 Two directions • In our terms, there are “Rust-first” or “Strictly schema-first” approaches. grpc-gateway GraphQL / Apollo FastAPI protobuf Go Ruby OpenAPI GraphQL TypeScript Python OpenAPI Part III > Schema-first

Slide 46

Slide 46 text

2019/10/26 “Making an opinionated Web framework” 46 Schemas: our decision • Follow NestJS and FastAPI way: from Rust type to OpenAPI. • We can augment it with gRPC or GraphQL later. Part III > Schema-first

Slide 47

Slide 47 text

2019/10/26 “Making an opinionated Web framework” 47 Error handling • We always use Result, but for E we have several choices. Trait Objects priv enum pub enum Box failure::Error pub enum MyError { IoError(…), … } tokio::timer::Error hyper::error::Error Part III > Error-handling

Slide 48

Slide 48 text

2019/10/26 “Making an opinionated Web framework” 48 Error handling: extra requirements •A framework must be able to turn errors into responses. •A framework usually has a default set of errors, but users may also want to define application-level errors. Part III > Error-handling

Slide 49

Slide 49 text

2019/10/26 “Making an opinionated Web framework” 49 Case I: actix-web • ResponseError trait does Web-specific things. • actix_web::Error works as Box.

Slide 50

Slide 50 text

2019/10/26 “Making an opinionated Web framework” 50 Case II: gotham • As of today, gotham::error is an alias for failure. • This is a good starting point before deciding how we finally handle errors.

Slide 51

Slide 51 text

2019/10/26 “Making an opinionated Web framework” 51 Errors and schemas • On one hand, we want to treat errors uniformly. • On the other hand, we want to describe errors precisely in the generated schemas. Different handlers return different errors. • Most frameworks sacrifice error preciseness (which seems basically rational)

Slide 52

Slide 52 text

2019/10/26 “Making an opinionated Web framework” 52 Error handling: our decision • A predefined enum NailsError, which is extendable by a trait ServiceError • Like trait objects, applications can arbitrarily add new errors. • An application is free to construct/inspect framework- defined errors.

Slide 53

Slide 53 text

2019/10/26 “Making an opinionated Web framework” 53 Error handling: our decision • Decouple error data and formatter using these interfaces: • Display implementation (private error message) • fn public_message(&self) (public error message) • fn class_name(&self) (error classification) • fn status(&self) (HTTP status code) • A formatter turns errors into responses. • Extract JSON schema from the formatter. • Optional runtime verification of error classes for better error description in the generated schema

Slide 54

Slide 54 text

2019/10/26 “Making an opinionated Web framework” 54 Configuration and DI •Configuration is something like DATABASE_URL, POOL_SIZE, or an actual pool of connections. •DI (Dependency Injection) is a replaceable/mockable component passed as a parameter.

Slide 55

Slide 55 text

2019/10/26 “Making an opinionated Web framework” 55 Configuration and DI •Configuration and DI are similar in that they’re globally shared parameters for the application. •I call them contexts in this presentation

Slide 56

Slide 56 text

2019/10/26 “Making an opinionated Web framework” 56 Configuration and DI •Due to its shared nature, it’s expected to be a (shallow-)cloneable object. •Configuration is a struct Arc •and DI is a trait object Arc

Slide 57

Slide 57 text

2019/10/26 “Making an opinionated Web framework” 57 Typed vs. Untyped contexts Typed Contexts • Handler • Router • Middleware • Ctx is application-defined Untyped Contexts • Handler • Router • Middleware • We extract an application- defined Ctx from WAF- defined AnyCtx

Slide 58

Slide 58 text

2019/10/26 “Making an opinionated Web framework” 58 Cases: actix-web 0.7.x vs. 1.x actix-web 0.7.x • App • HttpRequest • FromRequest • Middleware • State is the context extractor actix-web 1.x • App • HttpRequest • FromRequest • Transform (S isn’t a state) • Data is the context extractor (no connection with T in App)

Slide 59

Slide 59 text

2019/10/26 “Making an opinionated Web framework” 59 Really typed DI • Each injected dependency can have its own type parameter. • MyApp • I think for most cases, this is too verbose with relatively little performance and safety benefits.

Slide 60

Slide 60 text

2019/10/26 “Making an opinionated Web framework” 60 App and request contexts • Context may have states (with interior mutability). Mocked dependency usually has states. • Application-global states and request-local states are different. Instrumentations may want to store their statistics in both states. • How do we distinguish them?

Slide 61

Slide 61 text

2019/10/26 “Making an opinionated Web framework” 61 Context typedness: our decision • Use typed contexts. • Each application defines AppCtx and ReqCtx. • AppCtx + WAF’s request data → ReqCtx

Slide 62

Slide 62 text

2019/10/26 “Making an opinionated Web framework” 62 Context conversion: AsRef and Into • In a typed contexts setting, we sometimes need to convert between different contexts for better compositionality. • Question: AsRef or Into, which is better?

Slide 63

Slide 63 text

2019/10/26 “Making an opinionated Web framework” 63 Approach: AsContext • Similar to Into but clone-on-write. • Assume contexts to be always Clone. pub trait Context: std::fmt::Debug + Clone {} pub trait AsContext: Context { fn as_context(&self) -> Cow<'_, U>; } impl AsContext for T { fn as_context(&self) -> Cow<'_, T> { Cow::Borrowed(self) } }

Slide 64

Slide 64 text

2019/10/26 “Making an opinionated Web framework” 64 Approach: AsContext • We only pay costs on context type boundary. • As flexible as Into.

Slide 65

Slide 65 text

2019/10/26 “Making an opinionated Web framework” 65 Crate splitting • We want to implement boilerplates (or generators) in the near future (but not now) • Directory structure is an important aspect of opinionated frameworks.

Slide 66

Slide 66 text

2019/10/26 “Making an opinionated Web framework” 66 Layered architecture variants • There are several variants of layered architecture. • They’re common in that they enforce one-way dependency of layers. Persistence Domain Model Application Presentation

Slide 67

Slide 67 text

2019/10/26 “Making an opinionated Web framework” 67 1 layer = 1 crate? • In Rust world, one-way dependency is considered a good opportunity of crate splitting, but... app_presentation src Cargo.toml app_domain_service src Cargo.toml app_domain_model src Cargo.toml Isn’t this too verbose as a starting point?

Slide 68

Slide 68 text

2019/10/26 “Making an opinionated Web framework” 68 1 layer = 1 crate? • In Rust world, one-way dependency is considered a good opportunity of crate splitting, but... src app_application Cargo.toml app_domain_service Cargo.toml app_domain_model Cargo.toml This requires extra stanza in Cargo.toml

Slide 69

Slide 69 text

2019/10/26 “Making an opinionated Web framework” 69 1 layer = 1 crate? • In Rust world, one-way dependency is considered a good opportunity of crate splitting, but... src app_application Cargo.toml app_domain_service app_domain_model Mono-crate is concise and simple

Slide 70

Slide 70 text

2019/10/26 “Making an opinionated Web framework” 70 Crate splitting: our decision • I’m yet to decide this one… • We also have “hybrid” approach: • Initially, the generator generates a mono-crate tree. • At some stage, the programmer opt in to the splitted structure.

Slide 71

Slide 71 text

2019/10/26 “Making an opinionated Web framework” 71 Other questions raised • Generators / boilerplates / scaffolding • Middleware interfaces • Devserver and autoloading • Mocking • @asomers gave a great comparison and even made the greatest mocking library.

Slide 72

Slide 72 text

2019/10/26 “Making an opinionated Web framework” 72 Summary & Recap • Part I: we want to apply Rust for business-logic-heavy Web backends. • Part II: we started implementing WAF but it’s still a prototype. • Part III: there are interesting design questions about opinionated WAFs. Thanks!

Slide 73

Slide 73 text

2019/10/26 “Making an opinionated Web framework” 73 Recap: Part I: Before designing WAF • Background: my Rust interests • Background: my jobs • Single-feature servers • Domain servers • Why WAF now? • Our prototype Briefly describes why I started building WAF.

Slide 74

Slide 74 text

2019/10/26 “Making an opinionated Web framework” 74 Recap: Part II: Implementation • Example app: “conduit” • Async ecosystems • Structure • Nightly • Derive macros • Basic HTTP Tools • Diesel async wrapper • Overall progress Shows how our (partial) implementation is going.

Slide 75

Slide 75 text

2019/10/26 “Making an opinionated Web framework” 75 Recap: Part III: Design Questions • Schema-first development • Error handling • Configuration and DI • Crate splitting • Other questions raised Enumerates questions brought up during WAF design and our preliminary decisions.