React + Arquitectura limpia

⏳ 5 min

Aproximación a una arquitectura limpia con React y Typescript.

En esta ocasión quiero mostraros una aproximación a lo que sería una arquitectura limpia utilizando React + Typescript, y digo aproximación porque obviamente siempre se puede hacer mejor.

Primero de todo, ponte cómodo/a, prepárate una taza de ☕️ y vamos a ello.

El primer punto será cómo vamos a organizar nuestras carpetas, y así tener una vista general de cómo estará estructurado el proyecto.

proyecto/
├── src/
│   ├── Domain/
│   ├── Application/
│   ├── Infrastructure/
│   ├── Presentation/

Perfecto, ahora que ya tenemos una vista general de las estructura del proyecto, vamos a sumergirnos en cada una de estas ‘capas’.

Domain

Este es el corazón de nuestra aplicación. Aquí es donde definimos nuestras entidades de negocio, que son como los personajes principales de nuestra historia. Por ejemplo, si estuviéramos construyendo una aplicación de tipo blog, podríamos tener una entidad Post que represente una entrada de blog en sí misma. Además, aquí es donde residen nuestros repositorios, que son los elementos encargados de comunicarse con nuestra base de datos o cualquier otra fuente de datos externa.

Application

Esta capa es donde ocurre la magia. Aquí definimos nuestros casos de uso, que son como las aventuras que nuestros personajes (las entidades) van a vivir. Siguiendo con nuestro ejemplo de la aplicación de blog, podríamos tener un caso de uso llamado getPosts que se encargue de todo el proceso de pedir el listado de entradas de blog; otro ejemplo sería createPost, que se encargará de todo el proceso de creación de una nueva entrada. Esta capa actúa como el director de la orquesta, coordinando cómo se utilizan nuestras entidades y repositorios para lograr los objetivos de la aplicación. En definitiva, es donde se encuentra la lógica de negocio.

Infrastructure

Aquí es donde todo se conecta con el mundo exterior. Tenemos nuestros Data Transfer Objects (DTOs), que son como los mensajeros que llevan la información de un lugar a otro, especialmente útiles cuando trabajamos con servicios web o APIs. También encontramos nuestras implementaciones concretas de los repositorios, donde definimos cómo interactuar con nuestras fuentes de datos reales, ya sea una base de datos, una API REST o cualquier otra cosa. Y por último, pero no menos importante, aquí también podemos encontrar utilidades para manejar cosas como, por ejemplo, solicitudes HTTP.

Presentation

Esta es la capa donde la magia de la experiencia de usuario (UX) cobra vida. Es donde nuestras interfaces de usuario (UI) toman forma. Aquí es donde definimos nuestros componentes de React (o cualquier otro framework que estemos usando) y nuestras páginas.

Con estas cuatro capas bien definidas y organizadas, tenemos una base sólida para construir una aplicación robusta, mantenible y escalable.

Pasamos a la acción

Llegados a este punto… ¡creo que va tocando ver cómo se traduce toda esta teoría en código real!

¡Vamos a ello!

Capa de Dominio

// Domain/entities/Post.ts
export interface Post {
  id: number
  title: string
  body: string
}
// Domain/repositories/postRepository.ts
export interface PostRepository {
  getPosts: () => Promise<Post[]>
  // ...resto de métodos
}

Capa Aplicación

// Application/useCases/getPostUseCase.ts
export class GetPostsUseCase {
  postRepository: PostRepositoryImpl
  constructor() {
    this.postRepository = new PostRepositoryImpl()
  }

  execute() {
    return this.postRepository.getPosts()
  }
}

Capa de Infrastructura

// Infrastructure/DTOs/PostDTO.ts
export interface PostDTO {
  id: number
  title: string
  body: string
  userId: number
}

En este punto te muestro una implementación de una utilidad para manejar peticiones HTTP:

// Infrastructure/http/index.ts
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"
type Headers = {
  [key: string]: string
}
type IHttp<TBody> = {
  token?: string | null
  url: string
  method?: HttpMethod
  headers?: Headers
  body?: TBody
}

async function Http<TResponse, TBody>(options: IHttp<TBody>): Promise<TResponse> {
  const {
    token,
    url,
    method = "GET",
    headers = { ContentType: "application/json" },
    body,
  } = options

  const authToken = {
    ...(headers || {}),
    ...(token ? { Authorization: `Bearer ${token}` } : {}),
  }
  const response = await fetch(url, {
    method,
    headers: { ...headers, ...authToken },
    body: body ? JSON.stringify(body) : undefined,
  })
  return await response.json()
}

export default Http

Y continuamos con la implementación del repositorio:

export class PostRepositoryImpl implements PostRepository {
  async getPosts(): Promise<Post[]> {
    const posts: PostDTO[] = await http({
      url: "https://jsonplaceholder.typicode.com/posts",
      method: "GET",
    })

    return posts.map((postDTO): Post => {
      return {
        id: postDTO.id,
        title: postDTO.title,
        body: postDTO.body,
      }
    })
  }
}

Capa de presentación

Vamos con un par de ejemplos de UI con los que mostraremos el listado de entradas:

Un componente:
// Presentation/components/PostItem.tsx
export default function PostItem({ id, title, body }: Post) {
  return (
    <li key={id}>
      <h3>{title}</h3>
      <p>{body}</p>
    </li>
  )
}
Una página:
// Presentation/pages/PostsPage.ts
export default function DemoPage() {
  const [posts, setPosts] = useState<Post[] | null>(null)

  const handleGetPosts = async () => {
    const postsService = new GetPostsUseCase()
    const response = await postsService.execute()
    setPosts(response)
  }

  useEffect(() => {
    handleGetPosts()
  }, [])

  return (
    <>
      <ul>
        {posts &&
          posts.map((item) => {
            return <PostItem id={item.id} title={item.title} body={item.body} />
          })}
      </ul>
    </>
  )
}

¡Y esto es todo!

🎉 Hemos hecho un pequeño recorrido por los fundamentos de cómo estructurar una aplicación con React y TypeScript usando arquitectura limpia. Desde organizar nuestras carpetas hasta escribir el código para cada capa.

Espero que ahora tengas una mejor idea de cómo mantener tu proyecto bien organizado y fácil de mantener.

💪 Recuerda, una buena estructura no solo hace que tu código sea más limpio y manejable, sino que también facilita la vida de cualquier desarrollador que trabaje contigo (¡o incluso a ti mismo en el futuro!). Así que, la próxima vez que empieces un proyecto, no subestimes el poder de una buena arquitectura.

Happy coding 💻

👈 Volver