334 lines
6.8 KiB
Org Mode
334 lines
6.8 KiB
Org Mode
|
#+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~
|
||
|
|