This article shares the concept and implementation of the external resource unit in frontend applications within the Clean Architecture.
Repository with example:
https://github.com/harunou/react-tanstack-react-query-clean-architecture
The external resource unit represents a specific external data source or service that a frontend application interacts with.
External Resource Implementation
The implementation of this unit relies solely on the interface of the actual external resource.
All development focus is on the real external resource interface and its representation within the application.
By the end of the external resource unit implementation, developers should be able to understand the available remote API based on the implemented unit, without needing to consult backend documentation directly.
Let's look at an example of an external resource representing an Orders API provided by a backend service.
The first step is to obtain the external resource declaration in any API description format or documentation. In this case, we use OpenAPI:
openapi: 3.0.0
info:
title: Orders API
version: 1.0.0
servers:
- url: /api
paths:
/orders:
get:
summary: Get all orders
responses:
'200':
description: List of orders
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/order'
/orders/{id}:
get:
summary: Get order by ID
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'200':
description: Order details
content:
application/json:
schema:
$ref: '#/components/schemas/order'
put:
summary: Update order by ID
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/order'
responses:
'204':
description: No content
delete:
summary: Delete order by ID
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
'204':
description: No content
components:
schemas:
order:
type: object
properties:
id:
type: string
userId:
type: string
items:
type: array
items:
$ref: '#/components/schemas/item'
required:
- id
- userId
- items
item:
type: object
properties:
id:
type: string
productId:
type: string
quantity:
type: integer
required:
- id
- productId
- quantity
Next, we implement the external resource unit in the frontend application, beginning with types and interfaces. These types can be generated automatically by a specialized code generator based on the OpenAPI declaration above or a similar tool.
export interface ApiOrderDto {
id: string;
userId: string;
items: ApiOrderItemDto[];
}
export interface ApiOrderItemDto {
id: string;
productId: string;
quantity: number;
}
Finally, we implement the external resource unit as shown below, with methods that directly reflect the API operations defined in the API service declaration.
const baseUrl = import.meta.env.BASE_URL;
export class OrdersApi {
static make(): OrdersApi {
return new OrdersApi(HttpClient.make());
}
private ordersApiUrl = `${baseUrl}api/orders`;
constructor(private readonly httpClient: ApiHttpClient) {}
async getOrders(): Promise<ApiOrderDto[]> {
const request = new Request(new URL(this.ordersApiUrl), {
method: "GET",
});
const response = await this.httpClient.request(request);
return response.json();
}
async getOrder(id: string): Promise<ApiOrderDto> {
const request = new Request(new URL(`${this.ordersApiUrl}/${id}`), {
method: "GET",
});
const response = await this.httpClient.request(request);
return response.json();
}
async updateOrder(id: string, order: Partial<ApiOrderDto>): Promise<void> {
const request = new Request(new URL(`${this.ordersApiUrl}/${id}`), {
method: "PUT",
body: JSON.stringify(order),
});
await this.httpClient.request(request);
}
async deleteOrder(id: string): Promise<void> {
const request = new Request(new URL(`${this.ordersApiUrl}/${id}`), {
method: "DELETE",
});
await this.httpClient.request(request);
}
}
At this stage, the external resource unit is ready to be used in the application.
Q&A
How should the external resource unit be tested?
In practice, the best way to test it is through integration tests with the gateway unit, using libraries like MSW. An example integration test is available here:
RemoteOrdersGateway.spec.ts
Where should the external resource unit be placed?
The unit should be placed in a dedicated api
directory.
Can the external resource unit use entity types instead of declaring its own?
It is generally recommended that the external resource unit declare its own types that directly reflect the structure of the external API. The external resource unit and the application core exist independently within the application; they do not know about each other and do not share anything between them. The only component that connects the core and the external resource is the gateway unit.
Conclusion
The external resource unit represents a specific external resource or service in a frontend application. By encapsulating API details and aligning with backend contracts, it enables seamless integration and enhances the developer experience.
Top comments (0)