@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