Slide 1

Slide 1 text

Introducing structure with Jan Stępień @janstepien :clojureD, Berlin 2018

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

@cljmuc

Slide 4

Slide 4 text

Let’s talk about structure

Slide 5

Slide 5 text

Let’s talk about structure of web apps

Slide 6

Slide 6 text

Architecture off the shelf

Slide 7

Slide 7 text

There are no frameworks* * well, actually

Slide 8

Slide 8 text

This is just one recipe

Slide 9

Slide 9 text

Ports and adapters Hexagonal Clean

Slide 10

Slide 10 text

@janstepien Layered structure

Slide 11

Slide 11 text

Domain entities @janstepien

Slide 12

Slide 12 text

Use cases and pure logic @janstepien

Slide 13

Slide 13 text

Adapters and the outer world @janstepien

Slide 14

Slide 14 text

@janstepien Dependencies point only inwards

Slide 15

Slide 15 text

Booking tables

Slide 16

Slide 16 text

src └── table ├── entities │ └── booking.clj ├── use_cases │ └── book_table.clj └── adapters └── ring.clj Domain entities Use cases Adapters

Slide 17

Slide 17 text

(ns table.entities.booking (:require [clojure.spec.alpha :as spec])) (spec/def ::booking (spec/keys :req-un [::name ::seats ::time])) (spec/def ::name string?) (spec/def ::seats (spec/and integer? pos?)) (spec/def ::time inst?)

Slide 18

Slide 18 text

(spec/explain ::booking {:name "რუსთაველი" :seats 0 :time #inst "2017-06-06T21:00Z"})) (spec/valid? ::booking {:name "Stępień" :seats 3 :time #inst "2017-06-06T21:00Z" :phone "+49876543210"}))

Slide 19

Slide 19 text

(gen/sample (spec/gen ::booking)) #_=> ({:name "ldeDMP9" :seats 3122 :time #inst "1969-12-31T23:59:59.999Z"} {:name "C5bLbvKzGeRt94" :seats 1771071 :time #inst “1970-01-01T00:04:28.571Z"} ...)

Slide 20

Slide 20 text

src └── table ├── entities │ └── booking.clj ├── use_cases │ └── book_table.clj └── adapters └── ring.clj Domain entities Use cases Adapters

Slide 21

Slide 21 text

(ns table.use-cases.book-table (:require [table.entities.booking :as booking] [clojure.spec.alpha :as spec])) (defn book-table [context booking] {:pre [(spec/valid? ::booking/booking booking)]} (save-booking (:save-booking context) booking)) (defprotocol SaveBooking (save-booking [this booking]))

Slide 22

Slide 22 text

(let [booking {:name "Foo Bar" :seats 2 :time (java.util.Date.)} context {:save-booking (reify SaveBooking (save-booking [_ booking] (prn :saved booking)))}] (book-table context booking))

Slide 23

Slide 23 text

src └── table ├── entities │ └── booking.clj ├── use_cases │ └── book_table.clj └── adapters └── ring.clj Domain entities Use cases Adapters

Slide 24

Slide 24 text

(ns table.adapters.ring (:require [table.use-cases.book-table :as book-table])) (defn post-booking [req] (let [context (:table/context req) booking (coerce-params (:params req))] (book-table/book-table context booking) {:body "Success!", :status 201)) (defn list-bookings [req] ...)

Slide 25

Slide 25 text

(ns table.adapters.ring (:require [compojure.core :as comp :refer [GET POST]])) (defn routes [] (comp/routes (POST "/bookings" [] post—bookings) (GET "/bookings" [] list—bookings))) (defn wrap-context [handler context] (fn [req] (handler (assoc req :table/context context)))) (defn handler [context] (-> (routes) (wrap-context context) ...))

Slide 26

Slide 26 text

src └── table ├── entities │ └── booking.clj ├── use_cases │ └── book_table.clj ├── adapters │ └── ring.clj ├── system │ ├── postgres.clj │ └── http_server.clj └── system.clj @janstepien

Slide 27

Slide 27 text

(ns table.system.postgres (:require [com.stuartsierra.component :as component])) (defrecord Postgres [config connection] component/Lifecycle (start [this] (assoc this :connection (connect config))) (stop [this] (close connection) (assoc this :connection nil)))

Slide 28

Slide 28 text

(ns table.system.postgres (:require [table.use-cases.book-table :as book-table])) (extend-protocol book-table/SaveBooking Postgres (save-booking [postgres booking] (execute-sql postgres "insert into bookings..." booking))) (defn postgres [config] (map->Postgres {:config config}))

Slide 29

Slide 29 text

(ns table.system.http-server (:require [com.stuartsierra.component :as component])) (defrecord HttpServer [handler context server] component/Lifecycle (start [this] (assoc this :server (start-server (handler context)))) (stop [this] (stop-server server) (assoc this :server nil))) (defn http-server [handler] (map->HttpServer {:handler handler}))

Slide 30

Slide 30 text

(ns table.system (:require [com.stuartsierra.component :as component] [table.system [http-server :as http] [postgres :as postgres]] [table.adapters.ring :as ring])) (defn system [config] (component/system-map :postgres (postgres/postgres config) :context (component/using {} {:save-booking :postgres}) :http (component/using (http/http-server ring/handler) {:context :context})))

Slide 31

Slide 31 text

src └── table ├── entities │ └── booking.clj ├── use_cases │ └── book_table.clj ├── adapters │ └── ring.clj ├── system │ ├── postgres.clj │ └── http_server.clj ├── system.clj └── main.clj @janstepien

Slide 32

Slide 32 text

(ns table.main (:require [com.stuartsierra.component :as component] [table.system :as system])) (defn -main [] (let [config {:postgres {:host "localhost" :user "foo" :password "bar"} :http {:port 8080}}] (component/start (system/system config))))

Slide 33

Slide 33 text

src └── table ├── entities │ └── booking.clj ├── use_cases │ └── book_table.clj ├── adapters │ └── ring.clj ├── system │ ├── postgres.clj │ └── http_server.clj ├── system.clj └── main.clj

Slide 34

Slide 34 text

clojure.spec for contracts Ring and Compojure for HTTP Component and System for lifecycle Luminus or Duct for a framework

Slide 35

Slide 35 text

Introducing structure with Jan Stępień @janstepien :clojureD, Berlin 2018