deft/archives/pres-fp-architect-big-app.org

334 lines
6.8 KiB
Org Mode
Raw Permalink Normal View History

2019-04-01 11:14:52 +00:00
#+Title: Comment organiser du code fonctionnel
#+Author: Yann Esposito
#+Date: Mercredi 27 mars 2019
* Design Pattern en fonctionnel
- Programmation Orienté Objet => design pattern
- Fonctionnel => fonctions
* Fonctionnel
- LISP
- *ML
- Haskell
* Clojure
*** Fonctions / Fonctions partout !
- Utiliser des fonctions
initialiser:
#+BEGIN_SRC clojure
(defn main- [& args]
(do-thing-1)
(do-thing-2)
(do-thing-3))
#+END_SRC
*** Libs vs Applications
- Libs, un seul but précis.
- Applications, gestion de plusieurs sous fonctionalités.
*** Libs
Les libs presentent une liste de fonctions qui devraient être proche des
fonctions au sens mathématiques.
La valeur de retour de =(f x)= depend seulement de =x=. Pas de modification
d'état. Evaluer =(f x)= ne change pas l'état du système:
- pas de creation de fichier
- pas d'affichage sur l'écran
*** Applications
Gestions d'états internes, importations de plusieurs libs pour combiner
leur capacités.
- Serveur
- DB
- Logs
- Authentication
*** Combiner ; Expression Problem
The expression problem is a term used in discussing strengths and weaknesses of
various programming paradigms and programming languages.
#+BEGIN_QUOTE
The expression problem is a new name for an old problem. The goal is to define a
datatype by cases, where one can add new cases to the datatype and new functions
over the datatype, without recompiling existing code, and while retaining static
type safety (e.g., no casts).
Philip Wadler
#+END_QUOTE
*** Clojure Protocols
#+BEGIN_SRC clojure
(defprotocol P
(foo [x])
(bar-me [x] [x y]))
(deftype Foo [a b c]
P
(foo [x] a)
(bar-me [x] b)
(bar-me [x y] (+ c y)))
(bar-me (Foo. 1 2 3) 42)
= > 45
#+END_SRC
*** Protocoles utiles ?
Création d'une API standardisé pour plusieurs types différents.
Par exemple, notion de CRUD pour différentes DB.
#+BEGIN_SRC clojure
(defprotocol CRUD
(create [this id obj])
(read [this id])
(update [this id new-obj])
(delete [this id]))
#+END_SRC
#+BEGIN_SRC clojure
(deftype Redis [redis-conf] ...)
(deftype Elasticsearch [es-conf] ...)
(deftype Postgres [pg-conf] ...)
#+END_SRC
*** État en paramètre
Problème central en fonctionnel.
Gestion des états internes.
Functions:
#+BEGIN_SRC clojure
(ns main
(:require
[my.redis]
[my.http-server]))
(defn main- []
(let [redis-state (init-redis)
http-server-state (init-http-server)]
(do-stuff redis-state http-server-state))
#+END_SRC
La différence fondamentale, l'état est *explicite*.
Supérieur en pratique à l'organisation orienté objet
pour du code de taille moyenne à relativement grande.
Typiquement organisation en micro-services avec peu d'inter-dépendances.
** État en RAM dans un atom
- Rend l'état implicite
- Changement d'état thread safe
*** Exemple (1/2)
#+BEGIN_SRC clojure
(ns my.redis ...)
(def redis-state (atom {})
(defn init-redis []
(reset! redis-state
(new-redis-connections {:host "..." :port ...})))
(defn do-stuf [...]
(redis/do-stuff @redis-state ...))
#+END_SRC
*** Exemple (2/2)
#+BEGIN_SRC clojure
(ns main
(:require
[my.redis]
[my.http-server]))
(defn main- []
(init-redis)
(init-http-server)
(do-stuff))
#+END_SRC
** État central explicite
*** État central explicite ; exemple
#+BEGIN_SRC clojure
(def state (atom {}))
(def init []
(let [svc1-state (init-svc1)
svc2-state (init-svc1)
svc3-state (init-svc1)]
(reset! state {:svc1-st svc1-state
:svc2-st svc2-state
:svc3-st svc3-state})))
(def main- []
(init)
(do-things))
#+END_SRC
*** État central explicite ; tests
- Facile à comprendre
- Possibilité de logger les changements d'états (voir elm architecture)
- undo presque gratuit
Mais:
- Difficile d'organiser l'init avec beaucoup de sous-composants/services
- Difficile de tester en parallèle, il faut "dupliquer" les states.
** Graphes d'États
*** Graphes d'États ; Exemple
#+BEGIN_SRC clojure
(def main- []
(let [svc1 (new-svc1)
svc2 (new-svc2 svc1)
...
svcN (new-svcN svc1 svc5)
services {:svc1 svc1
:svc2 svc2
...
:svcN svcN}]
(do-things services)))
#+END_SRC
*** Graphes d'États ; Abstraction
- component / trapperkeeper
#+BEGIN_SRC clojure
(defprotocol CRUDService
(create [this id obj])
(read [this id])
(update [this id new-obj])
(delete [this id]))
(defservice crud-service
(init [this] ...)
(stop [this] ...)
(create [this id obj] ...)
(read [this id] ...)
(update [this id new-obj] ...)
(delete [this id] ...))
#+END_SRC
*** Graphes d'États ; Usage
fichier =bootstrap.cfg=
#+BEGIN_SRC
crud-service
db-service
http-server-service
#+END_SRC
fichier =config.edn= (ou =.json=, =.properties=, etc...)
#+BEGIN_SRC clojure
{:logging "dev-resources/logging.xml"
:server
{:user/user-web-service "/users"
:pizza/pizza-web-service "/pizzas"}
:jwt {:public-key "resources/cert.pub"
:private-key "resources/cert.key"}}
#+END_SRC
*** Usage réel
Choix du déploiement:
- plusieurs micro-services
- un seul gros super-service qui merge tous les micro-services
- un sous-ensemble de services
Avantages:
- scalabilité sur mesure
- Meilleur usage de la RAM
*** Purity
#+BEGIN_SRC clojure
(defservice MyService
[ConfigService
SubService]
(init [this context]
(let [more-context (core/my-init ConfigService)]
(merge context more-context)))
(stop [this] (core/my-stop (service-context this)))
(do-stuff [this]
(let [handlers (:handlers (service-context this))]
(core/do-stuff handlers ...))))
#+END_SRC
Dans le fichier =myservice/core.clj= les fn sont pures.
#+BEGIN_SRC clojure
(defn do-stuff [handlers obj]
(let [create (:redis-create handlers)]
(create object)))
#+END_SRC
*** Testing
Les fonctions de cores sont pures donc *reproductibles*.
Test de semi-intégration; les services déjà lancés et l'état initialisé.
#+BEGIN_SRC clojure
(with-app-with-config app services conf
(test-with-initiliazed-services ...))
#+END_SRC
* Haskell
** Règles *pragmatiques*
*** Organisation en fonction de la complexité
#+BEGIN_QUOTE
Make it work, make it right, make it fast
#+END_QUOTE
- Simple: directement IO (YOLO!)
- Medium: Haskell Design Patterns: The Handle Pattern:
https://jaspervdj.be/posts/2018-03-08-handle-pattern.html
- Gros: MTL / RIO / Free / Freeer / Effects...
*** Handler Pattern
#+BEGIN_SRC haskell
main = do
redisSvc <- newRedisHandler
serverSvc <- newServerHandler
doStuff redisSvc serverSvc
#+END_SRC
*** 3 couches
- *Imperatif*:
ReaderT IO
+ Insérer l'état dans une ~TVar~, ~MVar~ ou ~IORef~ (concurrence)
- *Orienté Objet*:
+ Handle / MTL / Free...
+ donner des access ~UserDB~, ~AccessTime~, ~APIHTTP~...
- *Fonctionnel*: Business Logic ~f : Handlers -> Inputs -> Command~