Implementación de un “Single Source of Truth” Basado en GraphQL: Integración de Go y TypeScript en tidy-core

Resumen

Este documento describe la implementación de un “Single Source of Truth” (SSOT) basado en GraphQL para la sincronización y consistencia de estructuras de datos y lógica de negocio en un sistema desarrollado en Go y TypeScript. Utilizando el esquema de Product como ejemplo central, se detalla cómo GraphQL se convierte en la fuente única y definitiva de verdad, asegurando la coherencia entre diferentes capas de la aplicación, minimizando errores y facilitando el mantenimiento y la escalabilidad del sistema tidy-core.

Introducción

En el desarrollo de software moderno, mantener la consistencia entre diferentes capas de la aplicación es un desafío crucial. La necesidad de sincronizar modelos de datos, validaciones, casos de uso y repositorios en múltiples lenguajes de programación puede generar inconsistencias y errores si no se maneja adecuadamente. Este paper explora cómo GraphQL, utilizado como “Single Source of Truth”, puede resolver este problema, tomando como ejemplo la implementación de la entidad Product en un sistema basado en Go y TypeScript llamado tidy-core.

Motivación

La principal motivación detrás de la adopción de GraphQL como SSOT es la necesidad de mantener una fuente centralizada de definiciones de datos que guíe todas las implementaciones en el sistema. Al utilizar GraphQL, se asegura que los modelos de datos, validaciones y casos de uso en diferentes lenguajes de programación (Go y TypeScript, en este caso) estén siempre alineados, reduciendo el riesgo de errores y facilitando el desarrollo de nuevas funcionalidades.

Estructura del Sistema

El sistema tidy-core se organiza en dos subcarpetas principales, una para Go y otra para TypeScript, cada una con su propia estructura modular. A continuación se describe la estructura de archivos para la entidad Product, con ejemplos detallados de cómo se implementa en Go y TypeScript.

Árbol de Archivos de Product

tidy-core/
├── go/
│ ├── domain/
│ │ ├── models/
│ │ │ └── product.go # Define las estructuras y tipos de `Product` en Go.
│ │ └── usecases/
│ │ └── product_usecases.go # Define la interfaz que incluye queries y mutations para `Product` en Go.
│ ├── repositories/
│ │ └── product_repository.go # Define el repositorio para la manipulación de `Product` en Go.
│ └── validations/
│ └── product_validations.go # Implementa las validaciones específicas para `Product` en Go, con soporte multidioma.

└── typescript/
├── domain/
│ ├── models/
│ │ └── product.ts # Define las interfaces y tipos de `Product` en TypeScript.
│ └── usecases/
│ └── productUseCases.ts # Define la interfaz que incluye queries y mutations para `Product` en TypeScript.
├── repositories/
│ └── productRepository.ts # Define el repositorio para la manipulación de `Product` en TypeScript.
└── validations/
├── product_validations_es.ts # Define las validaciones de Yup en español para `Product`.
└── product_validations_en.ts # Define las validaciones de Yup en inglés para `Product`.

Contenido de los Archivos

1. tidy-core/go/domain/models/product.go

package models

type Product struct {
ID string `json:"_id"`
Name string `json:"name"`
}

type PaginatedProduct struct {
Data []Product `json:"data"`
PageInfo PageInfo `json:"pageInfo"`
SearchId *string `json:"searchId,omitempty"`
Filters *string `json:"filters,omitempty"`
}

type ProductData struct {
Data Product `json:"data"`
}

type ProductListResponse struct {
PaginatedProduct *PaginatedProduct
Error *Error
}

type ProductEntityResponse struct {
ProductData *ProductData
Error *Error
}

type CreateProductInput struct {
Name string `json:"name"`
}

type UpdateProductInput struct {
Name string `json:"name"`
}

2. tidy-core/go/domain/usecases/product_usecases.go

package usecases

import "tidy-core/go/domain/models"

// ProductUseCases define la interfaz que incluye los contratos para los queries y mutations definidos en el esquema GraphQL.
type ProductUseCases interface {
// Queries
Products(first int, after string, orderBy string, dir string, searchId string) models.ProductListResponse
Product(id string) models.ProductEntityResponse

// Mutations
CreateProduct(input models.CreateProductInput) models.Product
UpdateProduct(id string, input models.UpdateProductInput) models.Product
DeleteProduct(id string) models.Product
}

3. tidy-core/typescript/domain/models/product.ts

export interface Product {
_id: string;
name: string;
}

export interface PaginatedProduct {
data: Product[];
pageInfo: PageInfo;
searchId?: string;
filters?: string;
}

export interface ProductData {
data: Product;
}

export type ProductListResponse = PaginatedProduct | Error;

export type ProductEntityResponse = ProductData | Error;

export interface CreateProductInput {
name: string;
}

export interface UpdateProductInput {
name: string;
}

4. tidy-core/typescript/domain/usecases/productUseCases.ts

import {
ProductListResponse,
ProductEntityResponse,
CreateProductInput,
UpdateProductInput,
Product,
} from '../models/product';

// ProductUseCases define la interfaz que incluye los contratos para los queries y mutations definidos en el esquema GraphQL.
export interface ProductUseCases {
// Queries
products(first?: number, after?: string, orderBy?: string, dir?: string, searchId?: string): ProductListResponse;
product(_id: string): ProductEntityResponse;

// Mutations
createProduct(input: CreateProductInput): Product;
updateProduct(_id: string, input: UpdateProductInput): Product;
deleteProduct(_id: string): Product;
}

5. tidy-core/go/repositories/product_repository.go

package repositories

import "tidy-core/go/domain/models"

type ProductRepository interface {
FindById(id string) (models.Product, error)
FindAll(first int, after, orderBy, dir, searchId string) ([]models.Product, error)
Create(product models.Product) (models.Product, error)
Update(id string, product models.Product) (models.Product, error)
Delete(id string) error
}

6. tidy-core/typescript/repositories/productRepository.ts

import { Product, CreateProductInput, UpdateProductInput } from '../models/product';

export interface ProductRepository {
findById(id: string): Promise<Product>;
findAll(first?: number, after?: string, orderBy?: string, dir?: string, searchId?: string): Promise<Product[]>;
create(product: CreateProductInput): Promise<Product>;
update(id: string, product: UpdateProductInput): Promise<Product>;
delete(id: string): Promise<void>;
}

7. tidy-core/go/validations/product_validations.go

package validations

import "errors"

var validationMessages = map[string]map[string]string{
"es": {
"name_required": "El nombre es obligatorio",
"name_length": "El nombre debe tener entre 3 y 50 caracteres",
},
"en": {
"name_required": "Name is required",
"name_length": "Name must be between 3 and 50 characters",
},
}

func ValidateProductName(name, lang string) error {
if len(name) < 3 || len(name) > 50 {
return errors.New(validationMessages[lang]["name_length"])
}
if name == "" {
return errors.New(validationMessages[lang]["name_required"])
}
return nil
}

8. tidy-core/typescript/validations/product_validations_es.ts

import * as yup from 'yup';

export const productNameValidationSchema = yup.object({
name: yup.string()
.required('El nombre es obligatorio')
.min(3, 'El nombre debe tener entre 3 y 50 caracteres')
.max(50, 'El nombre debe tener entre 3 y 50 caracteres'),
});

9. tidy-core/typescript/validations/product_validations_en.ts

import * as yup from 'yup';

export const productNameValidationSchema = yup.object({
name: yup.string()
.required('Name is required')
.min(3, 'Name must be between 3 and 50 characters')
.max(50, 'Name must be between 3 and 50 characters'),
});

Generación Automática de Código

El código para la entidad Product en Go y TypeScript fue autogenerado utilizando herramientas especializadas:

  • Go: Utilizamos gqlgen para generar automáticamente las estructuras y los tipos en product.go, así como las interfaces de casos de uso y repositorios. Esta herramienta se basa en el esquema GraphQL proporcionado, asegurando que el código generado esté alineado con las definiciones del esquema.
  • TypeScript: Para TypeScript, se utilizó una herramienta de generación automática que toma como entrada el esquema GraphQL y produce las interfaces, tipos y validaciones necesarias. Esta herramienta asegura que las definiciones de datos y validaciones en TypeScript sean consistentes con las de Go y el esquema GraphQL.

Conclusión

La adopción de GraphQL como “Single Source of Truth” en tidy-core ha demostrado ser una estrategia efectiva para mantener la coherencia y la integridad de los datos entre múltiples lenguajes de programación y capas de la aplicación. Este enfoque no solo reduce el riesgo de errores, sino que también facilita la escalabilidad y el mantenimiento del sistema. Con este marco establecido, tidy-core está bien posicionado para evolucionar de manera segura y eficiente, manejando la complejidad de un sistema de software moderno con múltiples componentes interconectados.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *