# Designing a back-end framework : callbacks types

**URL:** <https://discuss.ocaml.org/t/designing-a-back-end-framework-callbacks-types/15808>\
**Category:** Learning\
**Tags:** types, opium\
**Created:** [December 17, 2024, 5:09pm UTC](https://discuss.ocaml.org/t/designing-a-back-end-framework-callbacks-types/15808 "2024-12-17T17:09:52Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![frederic-lang](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/frederic-lang/32/6678_2.png) [@frederic-lang](https://discuss.ocaml.org/u/frederic-lang)\
**Post date:** [December 17, 2024, 5:09pm UTC](https://discuss.ocaml.org/t/designing-a-back-end-framework-callbacks-types/15808/1 "2024-12-17T17:09:52Z")

</div>

Hi,  
I am currently working on a back-end framework implemented with Opium.  
The main point of this framework is to declare the routes in tree-like structure and then be able to generate a describeApi endpoint that describe all the routes of the framework.

A main point of the framework is that routes are defined with two callback. the `hookCallback` is intended to retrieve various attributes from the request (as taskId header, etc…). the `callback` is user-defined and it is here that the actual process of the request happens.

The two callbacks are designed to be chained together. As this piece of code shows  
_microService.ml_

```auto
class microService = object(s)

  method routerDriver (app:Opium.App.t) =
    ...

  method routeDriver (route:RouteManager.routeEndpoint) (app:Opium.App.t)=
    App.action route.method_ route.path (fun r -> route.endpoint.callbacks.hookCallback r >>= (fun hookResponse -> route.endpoint.callbacks.callback (r, hookResponse)))

  method listen () = 
...
end

```

but what is a route endpoint ? it comes from the routeManager, which is responsible to register all the route and supply them when needed  
_routeManager.ml_

```auto
type 'a routeEndpoint = {
  path:string;
  method_:Opium.Method.t;
  endpoint: 'a Route.exposedRoute
}

class ['a] routeManager = object(s)
 ...

  method add (routeObj:'a Route.exposedRoute) = ...
  method getRouteList () : 'a routeEndpoint list = ...
...
end

let routeManager = new routeManager;;

```

the type `exposedRoute` comes from the file _Route.ml_ where the structure is like

```auto
type 'a callbacks = {
  callback: Request.t * 'a -> Response.t Lwt.t;
  hookCallback: Request.t -> 'a Lwt.t;
}

type 'a exposedRoute = {
  method_: Method.t;
  route:string;
  description: string;
  parameters: Parameters.exposedParameters;
  returns: Returns.exposedReturns;
  callbacks : 'a callbacks;
  componentId: string option;
  componentInputId: string option;
  componentOutputId: string option;
  purpose: string;
  nature: string option;
}

class ['a] route method_ route = object(s)
   method setDescription d = ...
   method setHookCallBack hc =...
   method setCallback c =...
   method expose () = ...
...
end

```

The route object is used to defined the route attributes and then build the exposed route type with the `expose` method.

1. my issue is that I have to chain the hookCallback and user callback. Then the hookCallback produce data consumed by the userCallback. the hookCallback is defined at a higher level in the framework, so I do not know in advance the data it produces.
2. My first solution was to used parametric polymorphism. So the types are defined with a type parameter `'a` that matches with the data produced by the hookCallback.
3. But this solution has two drawbacks. First, all the routes are linked with the same data produced. All the hookCallbacks for every routes have to produce the same data type 'a, which seems restrictive even if it could work.
4. Second, it triggers a error in the routeManager :

```auto
99 | let routeManager = new routeManager;;
         ^^^^^^^^^^^^
Error: The type of this expression, '_weak1 routeManager,
       contains type variables that cannot be generalized

```

So I cannot declare the routeManager without the implied `'a` type. I would declare the routeManager here so it is too restrictive.  
5. I have made an other attempt with the `'a.` quantifier  
_route.ml_

```auto
type 'a callbacks = {
  callback: Request.t * 'a -> Response.t Lwt.t;
  hookCallback: Request.t -> 'a Lwt.t;
}

type exposedRoute = {
  method_: Method.t;
  route:string;
  description: string;
  parameters: Parameters.exposedParameters;
  returns: Returns.exposedReturns;
  callbacks : 'a.'a callbacks;
  componentId: string option;
  componentInputId: string option;
  componentOutputId: string option;
  purpose: string;
  nature: string option;
}

```

but I get an error in my expose method, which I do not get :

```auto
68 | callbacks = {callback; hookCallback};
                         ^^^^^^^^^^^^^^^^^^^^^^^^
Error: This field value has type 'a callbacks which is less general than
         'a0. 'a0 callbacks

```

How can I declare a type `'a` that is specific to every route ?

---

<div class="post-metadata">

**Author:** ![monoidoid](https://avatars.discourse-cdn.com/v4/letter/m/ecc23a/32.png) [@monoidoid](https://discuss.ocaml.org/u/monoidoid)\
**Post date:** [December 17, 2024, 7:23pm UTC](https://discuss.ocaml.org/t/designing-a-back-end-framework-callbacks-types/15808/2 "2024-12-17T19:23:49Z")

</div>

The error with weak type variables only occurs because the `let routeManager` defines a top level module component, and there is no type to infer for this component. The same piece of code will work as a REPL statement, or if a `.mli` file is added in which `val routeManager` does not occur (or has a concrete instantiation for the type parameter).

You probably want the `'a` to be existentially quantified within the `callback` type, so using GADT syntax:

```auto
    type callback = Callback : { callback : Request.t * 'a -> Response.t Lwt.t; hookCallback: Request.t -> 'a Lwt.t} -> callback;;

```

Are you trying for some kind of builder pattern? If the `callback` and `hookCallback` need to have matching types, you cannot provide them one at a time, as `setHookCallback` and `setCallback` would imply.
