Développement sans fron-Tiers

@JoGrenat

Architecture 3-tiers

Présentation

Applicatif

Données

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[]

Frontières

Entre-Deux

Contexte / Dynamisme

Who is
this?

  • Sessions
  • Requêtes multiples
  • HATEOAS
{
  "_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)
};

Paradigme

C'est quoi
l'héritage ?

Et un
arbre ?

Performances et temps

  1. Sérialiser les données côté client
  2. Créer une requête HTTP côté client
  3. Créer un endpoint HTTP côté serveur
  4. Désérialiser les données côté serveur
  5. Créer une requête SQL
  6. Parser la requête SQL
  7. Désérialiser la réponse de la base
  8. Sérialiser la réponse pour le front
  9. Vérifier le status HTTP
  10. Désérialiser la réponse

🤯

Garanties

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

  • Validité des requêtes
  • Contrainte d'unicité
  • Références externes
  • Longueur de texte

Sécurité

L'analyse du flux d'information est difficile

  • Sécurité
  • Garanties
  • Contexte / Dynamisme
  • Temps / Performance
  • Paradigmes

Programmation multi-tiers

Présentation

Applicatif

Données

Programmation multi-tiers

  • Hop.js
  • Links
  • Ur/Web
  • Eliom/Ocsigen
  • Gavial
  • Opa
  • Swift 
  • SIF

Rien n'est nouveau

  • MeteorJs
  • GWT
  • ...

Le code back/front est co-localisé

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

  • Split d'une fonctionnalité
  • Moins de bruit dans le code
  • Moins d'erreurs possibles
  • Charge cognitive réduite
  • Maintenance facilitée
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

Désolé...

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

Alors pourquoi ?

Gain de temps

Charge cognitive réduite

Meilleure maintenabilité

 Nos types voyagent

  • Les requêtes à la DB
  • Les "appels serveur"
  • Le HTML
  • Nos types métiers
  • Problème d'impédance

 Partage de code

  • Server-side rendering
  • Gestion intégrée des états
  • Progressive enhancement
  • Communication bi-directionnelle
  • Contexte stocké côté client
  • Concurrence côté front

Programmation multi-tiers

JoinedChatroom room ->
  (model, sendToBackend (UserJoined { name = "Bob", room = room }))
UserJoined user ->
  (model, sendToFrontend clientId (UserGreeting ("Hello " ++ user.name)))
  • Couverture 3-tier + infrastructure
  • CI / CD automatiquement gérée
  • Elimination du code mort
  • Excellent tooling
  • Evergreen migrations

Java + Information Flow

Langage security-typed

int {Alice→Bob} x;
  • Assure la confidentialité de la DB au front en passant par le file system
     
  • Les données utilisateurs doivent être vérifiées
     
  • Formulaire générées de façon unique

Théorique

🪐

🪐

Pratique

🚀

🚀

🚀

🚀

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

Merci !

Slides : bit.ly/multitiers-fr

@JoGrenat

Développement sans Front-Tiers

By ereold

Développement sans Front-Tiers

  • 862