diff --git a/src/Metadata/OperationDef.php b/src/Metadata/OperationDef.php
index a4427f04d32eaf3aa43d0215b54291059cbb73da..a414781a0c000c13e3a196393f80c98e7572e90b 100644
--- a/src/Metadata/OperationDef.php
+++ b/src/Metadata/OperationDef.php
@@ -179,6 +179,19 @@ final class OperationDef implements \JsonSerializable, HasName
         return $this->isCollection;
     }
 
+    /**
+     * @return string
+     */
+    public function __toString(): string
+    {
+        return sprintf('operation "%s" (method=%s, isCollection=%s, special=%s)',
+            $this->originalName,
+            $this->method,
+            $this->isCollection ? "yes" : "no",
+            $this->special
+         );
+    }
+
     /**
      * {@inheritdoc}
      */
diff --git a/src/OperationMapper.php b/src/OperationMapper.php
index d820618be59a43a62323e02391c31f212fe7a4a7..9e3b1b872e77890924c6a8043885ef32a688e332 100644
--- a/src/OperationMapper.php
+++ b/src/OperationMapper.php
@@ -104,12 +104,9 @@ final class OperationMapper
         }
 
         $opParameters = $body = [];
-        $callParameters = [TypescriptHelper::quoteString($this->operation->getMethod())];
 
         $callPath = $path = $this->pathParser->parse($this->operation->getPath(), $this->operation->getRequirements());
 
-        $iriParameterName = null;
-
         if ($this->iri) {
             $iriParameter = new Parameter('iri', Placeholder::get(sprintf('IRI<%s>', $this->operation->getClassName())));
 
@@ -118,14 +115,12 @@ final class OperationMapper
 
         if ($callPath === $this->iri) {
             $opParameters[] = $iriParameter;
-            $iriParameterName = $iriParameter->getName();
-            $callParameters[] = $iriParameterName . ' as any';
+            $urlParameter =  $iriParameter->getName();
         } else {
             foreach ($callPath->getParameters() as $parameter) {
                 $opParameters[] = $parameter;
             }
-            $body[] = 'const path = ' . $callPath->getUsage() . ';';
-            $callParameters[] = 'path';
+            $urlParameter = $callPath->getUsage();
         }
 
         $denormalization = $this->operation->getDenormalization();
@@ -137,7 +132,6 @@ final class OperationMapper
             }
 
             $opParameters[] = new Parameter('body', $requestBody);
-            $body[] = 'options.body = body;';
         }
 
         $filterProperties = $this->getFilterProperties();
@@ -148,11 +142,10 @@ final class OperationMapper
         }
 
         $opParameters[] = new Parameter('options', Placeholder::get('RequestOptions'), false, '{}');
-        $callParameters[] = 'options';
 
-        $returnType = $responseBody ?: Placeholder::get('HttpResponseBase');
+        $returnType = $responseBody ?: BuiltinType::get('void');
 
-        $body[] = sprintf('return %s;', $this->buildClientCall($callParameters, $returnType, $iriParameterName));
+        $body[] = sprintf('return %s;', $this->buildClientCall(TypescriptHelper::quoteString($this->operation->getMethod()), $urlParameter, 'body', 'options', $returnType));
 
         return new Operation(
             $this->operation->getName(),
@@ -186,41 +179,41 @@ final class OperationMapper
     }
 
     /**
-     * @param array       $callParameters
-     * @param Type        $returnType
-     * @param string|null $iriParameterName
+     * @param string $method
+     * @param string $path
+     * @param string $body
+     * @param string $options
+     * @param Type   $returnType
      *
      * @return string
      */
-    private function buildClientCall(array $callParameters, Type $returnType, ?string $iriParameterName): string
+    private function buildClientCall(string $method, string $path, string $body, string $options, Type $returnType): string
     {
-        $clientCall = sprintf(
-            'this.client.request<%s>(%s)',
-            $returnType->getUsage(),
-            implode(', ', $callParameters)
-        );
-
         $opDef = $this->operation->getOpDef();
 
+        if ($opDef->isDeleteItem()) {
+            return "this.client.deleteItem($path, $options)";
+        }
+
+        $type = $returnType->getUsage();
+
         if ($opDef->isGetCollection()) {
-            return "this.cache.getAll($clientCall)";
+            return "this.client.getCollection<$type>($path, $options)";
         }
+
         if ($opDef->isCreateItem()) {
-            return "this.cache.post($clientCall)";
+            return "this.client.postItem<$type>($path, $body, $options)";
         }
-        if ($iriParameterName) {
-            if ($opDef->isGetItem()) {
-                return "this.cache.get($iriParameterName, () => $clientCall)";
-            }
-            if ($opDef->isUpdateItem()) {
-                return "this.cache.put($iriParameterName, $clientCall)";
-            }
-            if ($opDef->isDeleteItem()) {
-                return "this.cache.delete($iriParameterName, $clientCall)";
-            }
+
+        if ($opDef->isUpdateItem()) {
+            return "this.client.putItem<$type>($path, $body, $options)";
+        }
+
+        if ($opDef->isGetItem()) {
+            return "this.client.getItem<$type>($path, $options)";
         }
 
-        return $clientCall;
+        return "this.client.request<$type>($method, $path, $options)";
     }
 
     /**
diff --git a/src/Resources/views/common.ts.twig b/src/Resources/views/common.ts.twig
index cb466fa681f649859c2a5a1857110612cda7b04c..504de8a4a1fb576627156aa286fbb7ad9cab2f8f 100644
--- a/src/Resources/views/common.ts.twig
+++ b/src/Resources/views/common.ts.twig
@@ -1,7 +1,6 @@
 {% extends '@NgModelGenerator/_layout.ts.twig' %}
 
 {% block content %}
-import {HttpClient, HttpHeaders, HttpResponseBase} from '@angular/common/http';
 import { forkJoin, Observable } from 'rxjs';
 
 /**
@@ -46,7 +45,7 @@ export interface Resource {
   readonly [IRI_PROPERTY]: IRI<any>;
   readonly [TYPE_PROPERTY]: string;
   [property: string]: any;
-};
+}
 
 /**
  * Collection représente une collection de respoucres JSON-LD pour un type T donné.
@@ -92,62 +91,48 @@ export type DateTime = string;
  * Options supplémentaires pour les requêtes.
  */
 export interface RequestOptions {
-  body?: any;
-  headers?: HttpHeaders | {
-    [header: string]: string | string[];
-  };
+  headers?: { [header: string]: string | string[]; };
   params?: { [param: string]: string | string[]; };
 }
 
 /**
- * AbstractResourceCache est une classe abstraite qui définit l'interface d'un cache de ressources.
- *
- * Elle doit être implémentée puis être fournie en provider d'un module core. Par exemple:
- *
- *      final class ResourceCache<T extends Resource> extends AbstractResourceCache<T> {
- *        // Implémentation
- *      }
- *
- *      providers: [
- *        [ provider: AbstractResourceCache, useClass: ResourceCache ],
- *      ]
+ * Client d'API, à implémenter.
  */
-export abstract class AbstractResourceCache {
-  /**
-   * Récupère une ressource par son IRI. N'exécute la requête requestFactory que si on ne dispose
-   * pas d'une version en cache.
+export abstract class APIClient {
+   /**
+   * Récupère une ressource par son IRI.
    */
-  public abstract get<R extends Resource>(
-    iri: IRI<R>,
-    requestFactory: () => Observable<R>
-  ): Observable<R>;
+  public abstract getItem<R extends Resource>(iri: IRI<R>, options?: RequestOptions): Observable<R>;
 
   /**
-   * Met à jour une ressource existante, rafraîchit le cache local avec la réponse.
+   * Met à jour une ressource existante.
    */
-  public abstract put<R extends Resource>(iri: IRI<R>, request: Observable<R>): Observable<R>;
+  public abstract putItem<R extends Resource>(iri: IRI<R>, item: any, options?: RequestOptions): Observable<R>;
 
   /**
    * Crée une nouvelle ressource et met la ressource créée dans le cache.
    */
-  public abstract post<R extends Resource>(request: Observable<R>): Observable<R>;
+  public abstract postItem<R extends Resource>(iri: string, item: any, options?: RequestOptions): Observable<R>;
 
   /**
    * Supprime une ressource en distant et dans le cache.
    */
-  public abstract delete<R extends Resource>(iri: IRI<R>, request: Observable<HttpResponseBase>): Observable<HttpResponseBase>;
+  public abstract deleteItem<R extends Resource>(iri: IRI<R>, options?: RequestOptions): Observable<void>;
 
   /**
    * Effectue une recherche et met en cache toutes les ressources récupérées.
    */
-  public abstract getAll<R extends Resource>(
-    request: Observable<Collection<R>>
-  ): Observable<Collection<R>>;
+  public abstract getCollection<C extends Collection<Resource>>(iri: string, options?: RequestOptions): Observable<C>;
 
   /**
    * Invalide une ressource en cache.
    */
-  public abstract invalidate<R extends Resource>(iri: IRI<R>): void;
+  public abstract invalidateItem<R extends Resource>(iri: IRI<R>): void;
+
+  /**
+   * Effectue une requête arbitraire.
+   */
+  public abstract request<T>(method: string, url: string, options?: RequestOptions): Observable<T>;
 }
 
 /**
@@ -249,8 +234,7 @@ export class ResourceMetadata<R extends Resource, T extends string, P extends IR
 export abstract class AbstractRepository<R extends Resource, T extends string, P extends IRIParameters> {
   public constructor(
     public readonly metadata: ResourceMetadata<R, T, P>,
-    protected readonly client: HttpClient,
-    protected readonly cache: AbstractResourceCache
+    protected readonly client: APIClient
   ) {}
 
   /**
@@ -379,8 +363,7 @@ export abstract class AbstractAPIService<
   public constructor(
     public readonly metadata: MR,
     public readonly repositories: RR,
-    private readonly cache: AbstractResourceCache,
-    private readonly client: HttpClient
+    private readonly client: APIClient
   ) {}
 
   public get<R extends Resource>(iri: IRI<R>, options?: RequestOptions): Observable<R>;
@@ -398,7 +381,7 @@ export abstract class AbstractAPIService<
       iri = typeOrIRI as IRI<R>;
       options = parametersOrOptions as RequestOptions;
     }
-    return this.cache.get(iri, () => this.client.get<R>(iri as any, options));
+    return this.client.getItem<R>(iri, options);
   }
 
   public getMany<R extends Resource>(iris: IRI<R>[], options?: RequestOptions): Observable<R[]> {
@@ -410,7 +393,7 @@ export abstract class AbstractAPIService<
   }
 
   public invalidate<R extends Resource>(iri: IRI<R>): void {
-    this.cache.invalidate(iri);
+    this.client.invalidateItem(iri);
   }
 }
 
diff --git a/src/Resources/views/metadata.ts.twig b/src/Resources/views/metadata.ts.twig
index ae30a26c5f579d0d62b687e3fd3c4e9a3f45404d..164042134de91014cc4cab008e13e32ce03d33b9 100644
--- a/src/Resources/views/metadata.ts.twig
+++ b/src/Resources/views/metadata.ts.twig
@@ -5,11 +5,10 @@
 {% block content %}
 {% autoescape false %}
 import { Injectable, Provider } from '@angular/core';
-import { HttpClient } from '@angular/common/http';
 
 import {
   AbstractAPIService,
-  AbstractResourceCache,
+  APIClient,
   APIMeta,
   APIRepositoryRegistry,
   IRIMetadata,
@@ -93,13 +92,12 @@ export class AppMetadata extends LazyMetadataRegistry<AppAPI> {
 @Injectable()
 export class AppRepositories implements APIRepositoryRegistry<AppAPI> {
 {% for repo in repositories %}
-  public readonly {{ repo.resourceName }} = new {{ repo.name }}(this.metadata.{{ repo.resourceName }}, this.client, this.cache);
+  public readonly {{ repo.resourceName }} = new {{ repo.name }}(this.metadata.{{ repo.resourceName }}, this.client);
 {% endfor %}
 
   public constructor(
     public readonly metadata: AppMetadata,
-    private readonly cache: AbstractResourceCache,
-    private readonly client: HttpClient
+    private readonly client: APIClient
   ) {}
 
   public get<T extends keyof AppAPI>(type: T): AppAPI[T]['repository'] {
@@ -112,14 +110,6 @@ export class AppRepositories implements APIRepositoryRegistry<AppAPI> {
  */
 @Injectable()
 export class AppAPIService extends AbstractAPIService<AppAPI, AppMetadata, AppRepositories> {
-  public constructor(
-    metadata: AppMetadata,
-    repositories: AppRepositories,
-    cache: AbstractResourceCache,
-    client: HttpClient
-  ) {
-    super(metadata, repositories, cache, client);
-  }
 }
 
 /* Provider factories */
diff --git a/src/Resources/views/repositories.ts.twig b/src/Resources/views/repositories.ts.twig
index 688e7c0c2da98845ebee723c966dc4328309b331..c13f258347c260ebcffc527e4f7e566b316a0362 100644
--- a/src/Resources/views/repositories.ts.twig
+++ b/src/Resources/views/repositories.ts.twig
@@ -5,7 +5,6 @@
 {% block content %}
 {% autoescape false %}
 import { Observable } from 'rxjs';
-import { HttpResponseBase } from '@angular/common/http';
 
 // @ts-ignore
 import { AbstractRepository, Collection, IRI, RequestOptions, UUID } from './common';