class: middle # Raxx.Kit ## Lean mean web development [crowdhailer.me](https://crowdhailer.me) --- class: middle ## **Hi** ### *name -* Peter Saxton ### *@internets -* CrowdHailer ### *@works -* `nil` --- class: middle ## Raxx? ✋ ## Raxx.Kit? ✋ --- class: middle ![](/images/pebble-1.jpg) --- class: middle ## From the **beginning** ![](/images/difference-engine.jpg) --- class: middle ```bash mix archive.install hex raxx_kit mix raxx.new my_app ``` --- class: middle ```elixir defp deps do [ {:ace, "~> 0.18.6"}, {:raxx_logger, "~> 0.2.2"}, {:raxx_view, "~> 0.1.7"}, {:raxx_static, "~> 0.8.3"}, {:raxx_session, "~> 0.2.0"}, {:jason, "~> 1.0"}, {:exsync, "~> 0.2.3", only: :dev}, # ... ] end ``` *mix.exs* --- class: middle # What is Raxx? --- class: middle ## Raxx is **stable** ```elixir {:raxx, "~> 1.0"} ``` In **production** since 2017 Next door right now. --- class: middle ## Raxx is an **Interface** **Raxx** ```elixir defmodule MyApp.WWW do use Raxx.SimpleServer def handle_request(_request, _state) do %Raxx.Response{status: 200, headers: [], body: "Hello, World!"} end end ``` **erlang/OTP** ```elixir defmodule MyServer do use GenServer def handle_call(:request, _from, state) do {:reply, :response, state} end end ``` --- class: middle ## Raxx is an **Interface** ```elixir defmodule MyApp.WWW.Upload do use Raxx.Server # ! Raxx.SimpleServer def handle_head(%{path: ["upload"] body: true}, _) do {:ok, io_device} = File.open("my/path") {[], {:file, device}} end def handle_data(data, state = {:file, device}) do IO.write(device, data) {[], state} end def handle_tail(_trailers, state) do response(:see_other) |> set_header("location", "/") end end ``` --- class: middle ## Raxx is a **Toolkit** ```elixir defmodule MyApp.WWW.Greetings do use Raxx.SimpleServer def handle_request(request, _state) do query = Raxx.get_query(request) Raxx.response(:ok) |> Raxx.set_header("content-type", "text/plain") |> Raxx.set_body("Hello, World!") end end ``` *Raxx is imported by default.* --- class: middle ## Raxx is an **Ecosystem** ```elixir defmodule MyApp.WWW.Greetings do use Raxx.SimpleServer alias Raxx.Session use Raxx.View, arguments: [:user], template: "path/to/greetings_template.html.eex" def handle_request(request, %{session_config: conf}) do {:ok, session} = Session.extract(request, conf) response(:ok) |> render(session.user) end end ``` --- class: middle ## Raxx is an **Ecosystem** ```elixir Ace.HTTP.Service.start_link( {MyApp.WWW, config}, port: 8080, # ... ) ``` **Ace** - HTTP/(1 & 2) server to run **Raxx** applications. --- class: middle # What is Raxx.Kit? --- class: middle ### *Cowboy* -> **Ace** ### *Plug* -> **Raxx** ### *Phoenix* -> **???** --- class: middle ```sh mix raxx.new my_app --node-assets --ecto --docker --api ``` --- class: middle - project structure - live reloading - templates - static assets - testing - docker integration (`--docker`) - compiled assets (`--node-assets`) - database setup (`--ecto`) --- class: middle ![](/images/pebble-1.jpg) --- class: middle ## **C**apability
**C**onvenience
**C**onvention --- class: middle ## **Capability** #### the power or ability to do something - Ace - EExHTML --- class: middle ## **Convenience** #### the state of being able to proceed with something without difficulty - Raxx - Raxx.Session - Raxx.View *(works with Plug)* --- class: middle ## **Convention** #### behaviour that is considered acceptable or polite to most members of a society - Raxx.Kit --- class: middle # Principles --- class: middle ## Less is **More** > The cheapest, fastest, and most reliable components are those that aren’t there. *Gordon Bell* --- class: middle ## **Remove** unnecessary features **Old** ```elixir @route_name :users route ["users"], request do :GET -> # ... :POST -> # ... end ``` **New** ```elixir def handle_request(%{path: ["users"], method: :GET}, _) do # ... end def handle_request(%{path: ["users"], method: :POST}, _) do # ... end ``` --- class: middle ## Delegate ![](/images/js-tools.jpg) --- class: middle ## Delegate ```elixir Supervisor.child_spec({Task, fn() -> System.cmd("npm", ["run", "watch:js"], cd: "lib/my_app/www") end}, id: :watch_js), Supervisor.child_spec({Task, fn() -> System.cmd("npm", ["run", "watch:css"], cd: "lib/my_app/www") end}, id: :watch_css), ``` *--node-assets* --- class: middle ## Opt **in** #### Without a database ```sh mix raxx.new my_app mix phx.new my_app --no-ecto ``` #### With a database ```sh mix raxx.new my_app --ecto mix phx.new my_app ``` --- class: middle ## **M**ind your own business
**V**iew
**C**ontroller --- class: middle ![](/2018-08-16/mind-your-own-business-view-controller/hexagonal-architecture.png) --- class: middle - SQL - NoSQL - EventSourcing/CQRS - Memory Image - Hexagonal Architecture - Onion Architecture - **Bring your own** Raxx supports all of these architectures... -- because it assumes none of them --- class: middle ### **Libraries** > Frameworks ```elixir children = [ {MyApp.WWW, [[port: 4000]]} {MyApp.API, [[port: 4001]]} {MyApp.Admin, [[port: 4002]]} ] ``` *lib/my_app/application.ex* ```elixir setup do {:ok, service} = MyApp.WWW.start_link(%{}, port: 0) # etc end ``` *test/my_app/www_test.ex* --- class: middle ## Functional **cohesion** > #### Functional cohesion (best) > Functional cohesion is when parts of a module are grouped because they all contribute to a single well-defined task of the module. *https://en.wikipedia.org/wiki/Cohesion_(computer_science)* --- class: middle ## Functional **cohesion** ```sh my_app/ ├── www/ ├── actions/ ├── login.ex ├── login.html.eex ``` --- class: middle ## Functional **cohesion** ```elixir defmodule MyApp.WWW.Login do use Raxx.SimpleServer use Raxx.View, arguments: [] def handle_request(%{method: :GET}, _state) do response(:ok) |> render() end def handle_request(request = %{method: :POST}, _state) do # ... end end ``` --- class: middle # Future plans --- class: middle ## Stability ![](raxx-issues.png) --- class: middle ## Better **erlang** support ```erlang handle_request(_Request, _State) -> Response = raxx:response(ok), raxx:set_body(Response, <<"Hello, World">>). ``` --- class: middle ### New conventions - **Forms** ```elixir defmodule MyApp.API.CreatePost.Form do use Ecto.Schema @primary_key false embedded_schema do field(:pass_token, :binary_id) field(:idempotency_key, :string) end defp cast(raw) do keys = Map.keys(Map.from_struct(struct(__MODULE__))) Ecto.Changeset.cast(__MODULE__, raw, keys) |> Ecto.Changeset.apply_action(:insert) end end ``` --- class: middle ### New conventions - **Forms** ```elixir defmodule MyApp.API.CreatePost do use Raxx.SimpleServer alias MyApp.API.CreatePost.Form def handle_request(request, _state) do {:ok, payload} = Jason.decode(request.body) {:ok, data} = Form.cast(payload) end end ``` --- class: middle ### New conventions - **Error Handling** ```elixir defmodule MyApp.API.CreatePost do use Raxx.SimpleServer require OK def handle_request(request, _state) do OK.try do payload <- Jason.decode(request.body) data <- Form.cast(payload) after response(:created) rescue error -> MyApp.API.format_error(error) end end end ``` --- class: middle ![](quic-support.png) --- class: middle ## More **Buzzwords** ```sh mix raxx.new my_app --graphql --event_sourcing --kubernetes ``` --- class: middle # Questions? *@CrowdHailer*