dimanche 3 avril 2022

Design patterns of svelte (and any other client library) component when working with graphql

I had an argument with my colleague today regarding the design patterns of a svelte component we build, using graphql with urql library. We both came from backend world, and it looks like in the client there is a lot of confusion and non-standards regarding how to write code when using some library like react/svelte/vue. In short, we have a simple component that should fetch data from graphql server and display it in the page. The way I see it (according to the documentations), we should use the svelte binding and operationStore for all the logic (including mutations):

<script>
  import { operationStore, query } from '@urql/svelte';
  import { LIST_QUERY } from './queries';

  const todos = operationStore(LIST_QUERY);

  query(todos);
</script>

{#if $todos.fetching}
<p>Loading...</p>
{:else if $todos.error}
<p>Oh no... {$todos.error.message}</p>
{:else}
<ul>
  {#each $todos.data.todos as todo}
  <li>{todo.title}</li>
  {/each}
</ul>
{/if}

This is an example from the getting started page of the library. That way you use the full power of the library (changing variables changes the store and the page rerender, etc). If I look on other big graphql libraries like relay the same applies- All the graphql logic is done as part of the component. What about tests? Just mock the client like they suggest here (which annoying a bit, why I need to learn wonka to write tests? but that a different topic)

My friends say this code is bad from couple of reasons:

  1. You "getting married" with the library - If we will want to change from urql to something else in the future, it's gonna be a big refactor
  2. You "getting married" with graphql. What if we want to change to REST in the future? The component shouldn't care about where it gets the data from
  3. You can't use mocks that way- If you want to write the components before the server is ready you will need to modify the actual component code in order for it to work. Again the component is expose to where the data is coming from, which is bad.

He suggest building an interface controller like:

export interface TodosController {
    getTodos(): Promise<Todo[]>
    todoById(id: string): Promise<Todo>
    ....
}

It's implementation will be inject to the component with Context API, you can implement it using MockController, GraphqlController, or anything. The controller will handle the store logic etc.

From my experience with backend development and angular, all his points are valid, so why all the documentations suggest working with the first approach (i.e the components is tied with the clients)? They ignore the basic Model-View-Controller pattern, or just this is the "new way" to work with client libraries, and we are old?

Aucun commentaire:

Enregistrer un commentaire