@JoGrenat
Présentation
Applicatif
Données
Présentation
Applicatif
Données
Présentation
Applicatif
Données
JSON
Texte
Binaire
Statut HTTP
REST
GraphQL
Websocket
Requête SQL
Requête No-SQL
JSON
Data[]
Who is
this?
{ "_links": { "changePassword": { "href": "/api/user/1/change-password", "method": "POST", "payload": { "token": "80b3db96-06bb-4685-86a1-c115410210fd" } } } }
const token = getToken(user); return { payload: {token}, changePassword: (password: string) => doSomething(user, password) };
C'est quoi
l'héritage ?
Et un
arbre ?
🤯
type RemoteString = Loading | Loaded String | OnError Int String anError : RemoteString anError = OnError 400 "Bad Request" aSuccess : RemoteString aSuccess = Loading "Hello World!"
Type algébrique
type NonEmpty<a> = [a, ...a[]]; const invalid : NonEmpty<number> = [];
type NonEmpty<a> = [a, ...a[]]; const invalid : NonEmpty<number> = [];
type NonEmpty<a> = [a, ...a[]]; const invalid : NonEmpty<number> = [];
Liste non vide
L'analyse du flux d'information est difficile
Présentation
Applicatif
Données
export async function loader({ request }) { return getProjects(); } export async function action({ request }) { const form = await request.formData(); return createProject({ title: form.get("title") }); } export default function Projects() { const projects = useLoaderData(); const { state } = useTransition(); const busy = state === "submitting"; return ( <div> <Form method="post"> <input name="title" /> <button type="submit" disabled={busy}> {busy ? "Creating..." : "Create New Project"} </button> </Form> </div> );
Serveur
Client
router.get('/projects', async (req, res) => { const projects = await projectsRepository.getAll(); res.json({ projects }); }); router.post('/projects', async (req, res) => { const title = request.body.title; const project = await createProject({ title }); res.json({ project }); });
export default function Projects() { const [projects, setProjects] = useState([]); const [state, setState] = useState('idle'); const [title, setTitle] = useState(''); useEffect(() => { setState('busy'); fetch('/projects').then(({projects}) => { setState('loaded'); setProjects(projects) }).catch(() => setState('error')); }); const createProject = () => { fetch('/project', { method: 'POST', body: { title } }); } return ( <div> <!-- ... --> </div> );
Code lié à la communication
Code lié à la communication
export async function loader({ request }) { return getProjects(); } export async function action({ request }) { const form = await request.formData(); return createProject({ title: form.get("title") }); } export default function Projects() { const projects = useLoaderData(); const { state } = useTransition(); const busy = state === "submitting"; return ( <div> <Form method="post"> <input name="title" /> <button type="submit" disabled={busy}> {busy ? "Creating..." : "Create New Project"} </button> </Form> </div> );
Serveur
Client
Conventions
Sérialisation
Photo by Efe Kurnaz
fun completions(search) server { // Search words } fun suggest(search) client { domReplaceChildren( format(completions(search)), getNodeById("suggestions") ) } fun main() { var myhandler = spawnClient { fun receiver() { receive { case Suggest(search) -> suggest(search); receiver() } } receiver() }; page <body> <h1>Dictionary suggest</h1> <form l:onkeyup="{myhandler!Suggest(pre)}"> <input type="text" l:name="search" autocomplete="off"/> </form> <div id="suggestions"/> </body> } main()
module Echo_app = Eliom_registration.App (struct let application_name = "echo" let global_data_path = None end) let%server main_service = create ~path:(Path []) ~meth:(Get Eliom_parameter.unit) () let%server make_input up = let inp = Html.D.Raw.input () in let btn = Html.D.button ~a:[Html.D.a_class ["button"]] [Html.D.pcdata "Echo!"] in ignore [%client (Lwt.async (fun () -> Lwt_js_events.clicks (Html.To_dom.of_element ~%btn) (fun _ _ -> ~%up (Js.to_string (Html.To_dom.of_input ~%inp)##.value); Lwt.return_unit)) : unit) ]; Html.D.div [inp; btn] let%server () = Echo_app.register ~service:main_service (fun () () -> let item_up = Up.create (Eliom_parameter.ocaml "item" [%derive.json :string]) in let item_down = Down.of_react (Up.to_react item_up) in let list, handle = ReactiveData.RList.create [] in let list = ReactiveData.RList.map [%shared fun i -> Html.D.li [Html.D.pcdata i] ] list in let input = make_input item_up in ignore [%client (Eliom_client.onload (fun _ -> ignore (React.E.map (fun i -> ReactiveData.RList.cons i ~%handle) ~%item_down)) : unit) ]; Lwt.return (Eliom_tools.D.html ~title:"echo" (Html.D.body [input; Html.R.ul list])))
Code tiré de A Survey of Multitier Programming
PASCAL WEISENBURGER, JOHANNES WIRTH, and GUIDO SALVANESCHI,
Technische Universität Darmstadt, Germany
Séparation client / serveur
Communication
implicite vs explicite
implicite vs explicite
Granularité du split
expression, fonction, fichier
var db = database "todo"; var items = table "items" with (name : String) from db; mutual { fun showList() server { page <html> <body> <form l:action="{add(item)}" method="POST"> <input l:name="item"/> <button type="submit">Add item</button> </form> <table> {for (item <- query {for (item <-- items) [item]}) # HTML } </table> </body> </html> } fun add(name) server { insert items values [(name=name)]; showList() } fun remove(name) server { delete (r <-- items) where (r.name == name); showList() } } showList()
var db = database "todo"; var items = table "items" with (name : String) from db; mutual { fun showList() server { page <html> <body> <form l:action="{add(item)}" method="POST"> <input l:name="item"/> <button type="submit">Add item</button> </form> <table> {for (item <- query {for (item <-- items) [item]}) # HTML } </table> </body> </html> } fun add(name) server { insert items values [(name=name)]; showList() } fun remove(name) server { delete (r <-- items) where (r.name == name); showList() } } showList()
Séparation client / serveur
Communication
implicite vs explicite
implicite vs explicite
Couverture
client, serveur, base de données
Granularité du split
expression, fonction, fichier
JoinedChatroom room -> (model, sendToBackend (UserJoined { name = "Bob", room = room }))
JoinedChatroom room -> (model, sendToBackend (UserJoined { name = "Bob", room = room }))
UserJoined user -> (model, sendToFrontend clientId (UserGreeting ("Hello " ++ user.name)))
UserJoined user -> (model, sendToFrontend clientId (UserGreeting ("Hello " ++ user.name)))
Langage security-typed
int {Alice→Bob} x;
🪐
🪐
🚀
🚀
🚀
🚀
Remote Mob Programming
Pascal Weisenburger, Johannes Wirth, et Guido Salvaneschi, Technische Universität Darmstadt, Germany
Slides : bit.ly/multitiers-fr
@JoGrenat
Tous les papiers des langages
Slides : bit.ly/multitiers-fr
@JoGrenat