@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> = [];
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()
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 }))
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