Skip to content

sruta/twitter-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Acerca de este proyecto

El objetivo de este repositorio es mostrar como implementaría, a grandes rasgos, un sistema que tenga funcionalidades semejantes a Twitter (ahora X).

Construido con

  • Go como lenguaje de programación
  • Gin-Gonic para implementar la API REST
  • MariaDB para persistir los datos
  • JWT para autenticar a los usuarios

Estructura de carpetas

.
├── cmd
│   └── api
│       └── main.go     -> Punto de entrada. Configura el router y el contenedor.
├── docs                -> Documentación del sistema
├── internal            -> Código privado de la aplicación
│   ├── configs         -> Configuraciones de la aplicación
│   ├── domain          -> Estructuras que definen las entidades del sistema
│   ├── helpers         -> Funciones auxiliares que necesitan ser inicializadas
│   ├── infraestructure -> Código relacionado con la comunicación externa saliente
│   │   └── repository  -> Implementación de los repositorios para persistencia de datos
│   ├── interfaces      -> Código relacionado con la comunicación externa entrante
│   │   ├── controller  -> Funciones para manejar comunicación HTTP
│   │   └── dto         -> Objetos de transferencia de datos
│   ├── middleware      -> Middlewares para el framework gin-gonic
│   ├── usecase         -> Funciones que manejan la lógica de negocio
│   ├── test            -> Pruebas de la aplicación
│   └── container.go    -> Definición del contenedor para inyección de dependencias
├── pkg                 -> Código de librería que puede ser usado por aplicaciones externas
├── scripts             -> Scripts de base de datos
├── go.mod              -> Definición de dependencias de golang
└── README.md           -> Este archivo

El sistema está diseñado para usar inyección de dependencias basada en interfaces de golang. Esto permite realizar pruebas unitarias fácilmente en los controladores, servicios y repositorios, ya que podemos simular cada una de sus dependencias. También permite cambiar, por ejemplo, la tecnología de persistencia sin realizar grandes cambios en el código.

En este modelo, la autenticación se maneja mediante los middlewares de gin-gonic. Los controllers analizan las solicitudes HTTP, validando su cuerpo y parámetros, y también convierten errores de negocio en errores de API con el código de estado y formato correctos. Los usecases manejan la lógica de negocio y las validaciones, y orquestan el uso de las dependencias como clientes HTTP externos o clientes de persistencia. Finalmente, los repositories son los encargados de manejar la persistencia de los diferentes modelos del domain.

Instalación

  1. Clonar el repositorio

     git clone git@github.com:sruta/twitter-demo.git
  2. Utilizar docker para iniciar la base de datos y el sistema

    docker compose up -d --build
  3. Para el primer uso conectarse a la base de datos con las credenciales presentes en ./docker-compose.yml y crear el schema twitter_demo

  4. Ejecutar el script presente en ./scripts/setup_db.sql para crear las tablas necesarias

  5. La aplicación ya se encuentra lista para utilizar en http://localhost:8080

Tests

Se han implementado 3 tipos de tests a modo de ejemplo:

  1. Tests unitarios para usecase/user.go. Prueban los métodos mockeando la base de datos.

    go test -v ./internal/usecase
  2. Tests de integración para controller/user.go. Prueban los métodos utilizando la implementación real del usecase.

    go test -v ./internal/interfaces/controller
  3. Tests end-to-end para la API. Para ejecutarlo primero se debe iniciar la base de datos y la aplicación utilizando docker compose.

    go test -v ./test/e2e/

El test end-to-end realiza las siguientes solicitudes mientras va validando los resultados:

  1. Creación de un usuario A
  2. Creación de un usuario B
  3. Login del usuario A
  4. Login del usuario B
  5. Obtención del usuario A por ID
  6. Obtención del usuario B por ID
  7. Creación de un tweet para el usuario B
  8. Obtención de un timeline vacío para el usuario A
  9. Creación de un follower del usuario A al usuario B
  10. Obtención de un timeline con un tweet para el usuario A

Modelo de datos

data_model.png

Arquitectura actual

actual_architecture.png

Arquitectura escalable

Para poder soportar una mayor carga se debe poder escalar los distintos componentes del sistema de manera independiente según la necesidad. El siguiente enfoque separa el sistema según los distintos flujos de información:

  • Escrituras de tweets
  • Lecturas de timelines
  • Búsquedas de tweets/usuarios/tags

scalable_architecture.png

Componentes

  • Load Balancer: distribuye la carga entre los distintos scopes según el tipo de operación sobre los datos
  • CDN: almacena las imágenes/videos para que se distribuya más rápido a los usuarios
  • Write API Scope: maneja las operaciones de escritura sobre los datos. Se encarga de recibir los tweets, likes, followers, etc. Este scope tiene un sistema de colas para desacoplar la escritura de los tweets y la creación de los timelines.
  • Read API Scope: maneja las operaciones de lectura sobre los datos. Se encarga de recibir las solicitudes de timelines, tweets, etc. Utiliza caches para mejorar la velocidad de respuesta.
  • Search API Scope: maneja las operaciones de búsqueda sobre los datos. Se encarga de recibir las solicitudes de búsqueda de tweets, usuarios, tags, etc. Utiliza un motor de búsqueda especial para mejorar la velocidad de respuesta.
  • Media Store: almacena los videos/imágenes de los tweets.
  • Relational Database: almacena los datos de los usuarios, tweets, followers, etc. Utiliza réplicas para mejorar la velocidad de respuesta, los scopes de write interactúan con la instancia master y los scopes de read interactúan con las instancias replica.
  • Key-Value Store: almacena los timelines de los usuarios y tweets. Es la primera opción de búsqueda para el scope de read antes de ir a la base de datos relacional.
  • Full Text Search Engine: almacena de manera optimizada los tweets, usuarios y tags para su posterior búsqueda.
  • Graph Database: almacena las relaciones entre los usuarios (followers/followed).

Quiero que soporte más lecturas! Asi no vamos a conseguir inversión de los VCs

El diseño puede seguir iterándose para mejorar la escalabilidad, la velocidad de respuesta y separación de responsabilidades. Pueden existir scopes de read y write para cada una de las entidades del sistema (tweets, usuarios, timelines, search, media, etc.) de modo de poder escalar cada uno de ellos de manera independiente.

Uso de la API

Endpoints sin autenticación

  • Crear un nuevo usuario:

    POST /api/v1/user
    

    Cuerpo:

    {
      "email": "un_email@un_dominio.com",
      "password": "12345",
      "username": "un_nombre_de_usuario"
    }

    Respuesta exitosa:

    {
      "id": 1,
      "email": "un_email@un_dominio.com",
      "username": "un_nombre_de_usuario",
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-01-01T00:00:00Z"
    }
  • Login:

    POST /api/v1/login
    

    Cuerpo:

    {
      "email": "un_email@un_dominio.com",
      "password": "12345"
    }

    Respuesta exitosa:

    {
      "token": "un_token_JWT"
    }

Endpoints con autenticación

El header Authorization: Bearer {token_recibido_en_el_login} debe ser enviado en las solicitudes para poder acceder a los siguientes recursos.

  • Obtener un usuario por ID:

    GET /api/v1/user/:id
    

    Respuesta exitosa:

    {
      "id": 1,
      "email": "un_email@un_dominio.com",
      "username": "un_nombre_de_usuario",
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-01-01T00:00:00Z"
    }
  • Modificar un usuario por ID:

    PUT /api/user/:id
    

    Cuerpo:

    {
      "id": 1,
      "username": "un_nombre_de_usuario_editado"
    }

    Respuesta exitosa:

    {
      "id": 1,
      "email": "un_email@un_dominio.com",
      "username": "un_nombre_de_usuario_modificado",
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-02-01T00:00:00Z"
    }
  • Crear un tweet:

    POST /api/v1/tweet
    

    Cuerpo:

    {
      "user_id": 1,
      "text": "el_texto_de_un_tweet"
    }

    Respuesta exitosa:

    {
      "id": 1,
      "user_id": 1,
      "text": "el_texto_de_un_tweet",
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-01-01T00:00:00Z"
    }
  • Obtener un tweet por ID:

    GET /api/v1/tweet/:id
    

    Respuesta exitosa:

    {
      "id": 1,
      "user_id": 1,
      "text": "el_texto_de_un_tweet",
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-01-01T00:00:00Z"
    }
  • Modificar un tweet por ID:

    PUT /api/tweet/:id
    

    Cuerpo:

    {
      "id": 1,
      "user_id": 1,
      "text": "el_texto_de_un_tweet_modificado"
    }

    Respuesta exitosa:

    {
      "id": 1,
      "user_id": 1,
      "text": "el_texto_de_un_tweet_modificado",
      "created_at": "2025-01-01T00:00:00Z",
      "updated_at": "2025-02-01T00:00:00Z"
    }
  • Crear un follower:

    POST /api/v1/follower
    

    Cuerpo:

    {
      "follower_id": 1,
      "followed_id": 2
    }

    Respuesta exitosa:

    {
      "follower_id": 1,
      "followed_id": 2,
      "created_at": "2025-01-01T00:00:00Z"
    }
  • Obtener el timeline del usuario logueado:

    GET /api/v1/timeline
    

    Respuesta exitosa:

    [
      {
        "id": 1,
        "user_id": 1,
        "text": "el_texto_de_un_tweet",
        "created_at": "2025-01-01T00:00:00Z",
        "updated_at": "2025-01-01T00:00:00Z",
        "user": {
          "id": 1,
          "email": "un_email@un_dominio.com",
          "username": "un_nombre_de_usuario",
          "created_at": "2025-01-01T00:00:00Z",
          "updated_at": "2025-01-01T00:00:00Z"  
        } 
      }
    ]

Respuesta errónea

Cuando la solicitud no es exitosa, el servidor responde con el siguiente formato:

{
  "code": 400,
  "message": "una_descripcion_del_error"
}

Códigos de respuesta

  • 200 para solicitudes GET, PUT y DELETE exitosas
  • 201 para solicitudes POST exitosas
  • 400 para solicitudes fallidas cuando el cliente envía datos incorrectos
  • 401 para solicitudes fallidas cuando el cliente debería estar autenticado y no lo está
  • 403 para solicitudes fallidas cuando el cliente no está autorizado para realizar la acción
  • 404 para solicitudes fallidas cuando la entidad solicitada no se encuentra
  • 500 para solicitudes fallidas cuando el sistema falla por sí mismo

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published