Introducción
El manejo eficiente y flexible de la creación y actualización de entidades es un reto común en el desarrollo de aplicaciones complejas. En el sistema TidyRocks, se ha adoptado un enfoque innovador mediante el uso de dos funciones genéricas: TransformAndSet y TransformMergeAndSet. Estas funciones permiten transformar los datos de entrada en estructuras internas y aplican reglas adicionales mediante setters, proporcionando un nivel de control dinámico en las operaciones de creación y actualización.
Este paper se enfoca en cómo estas funciones han transformado la manera de manejar estas operaciones, garantizando un código limpio, reutilizable y fácilmente adaptable a nuevas necesidades.
Creación de Entidades: TransformAndSet
La función TransformAndSet permite transformar un tipo de entrada, como CreateCustomerInput, en una entidad interna como Customer, mientras ofrece la posibilidad de aplicar modificaciones adicionales a través de setters. Esta flexibilidad es fundamental para garantizar que ciertos campos puedan ser controlados por el programador sin tener que duplicar la lógica de creación.
Definición de TransformAndSet:
func TransformAndSet[T any, B any](input T, setters ...func(*B)) *B {
// Convertir de T a B usando JSON como intermediario
output := new(B)
data, _ := json.Marshal(input)
json.Unmarshal(data, output)
// Aplicar los setters adicionales proporcionados
for _, setter := range setters {
setter(output)
}
return output
}
Caso de uso: SignUp y CreateUser
En las operaciones de creación de usuarios (SignUp, CreateUser), TransformAndSet permite que los datos de entrada del usuario sean convertidos en una entidad User, mientras los setters aplican modificaciones específicas, como la verificación de correo electrónico.
func (u *UserUsecase) SignUp(ctx context.Context, input models.SignUpInput) (*User, TidyError) {
user := utils.TransformAndSet[models.SignUpInput, User](input, SetUserFields(false))
return u.Repo.Create(ctx, user)
}
func (u *UserUsecase) CreateUser(ctx context.Context, input models.CreateUserInput) (*User, TidyError) {
user := utils.TransformAndSet[models.CreateUserInput, User](input, SetUserFields(false))
return u.Repo.Create(ctx, user)
}
Flexibilidad mediante setters
La función SetUserFields permite modificar campos específicos de la entidad User de manera compacta:
goCopiar códigofunc SetUserFields(isEmailVerified bool) func(*User) {
return func(u *User) {
u.IsEmailVerified = isEmailVerified
}
}
Este enfoque permite al programador establecer dinámicamente valores clave en las entidades, sin tener que depender de lógica compleja o difícil de mantener en los casos de uso.
Actualización de Entidades: TransformMergeAndSet
Para las operaciones de actualización, donde los datos nuevos deben fusionarse con los datos existentes, la función TransformMergeAndSet sobresale. Esta función toma los datos recibidos (UpdateCustomerInput), los combina con los datos previamente guardados (Customer), y permite aplicar modificaciones adicionales mediante setters, proporcionando un control granular sobre qué campos se deben actualizar.
Definición de TransformMergeAndSet:
func TransformMergeAndSet[T any, B any](input T, savedData *B, setters ...func(*B)) *B {
// Convertir input a un mapa para inspeccionar los campos recibidos
inputMap := make(map[string]interface{})
data, _ := json.Marshal(input)
json.Unmarshal(data, &inputMap)
// Convertir savedData a un mapa para hacer merge
savedMap := make(map[string]interface{})
savedDataBytes, _ := json.Marshal(savedData)
json.Unmarshal(savedDataBytes, &savedMap)
// Fusionar los campos recibidos en input con los de savedData
for key, value := range inputMap {
if value != nil {
savedMap[key] = value
}
}
// Convertir el mapa combinado de vuelta a la estructura original
output := new(B)
mergedData, _ := json.Marshal(savedMap)
json.Unmarshal(mergedData, output)
// Aplicar los setters adicionales
for _, setter := range setters {
setter(output)
}
return output
}
Caso de uso: UpdateCustomer
En el caso de UpdateCustomer, la función TransformMergeAndSet permite fusionar los datos enviados por el usuario con los datos almacenados en la base de datos, y luego aplicar modificaciones adicionales de forma dinámica.
func (u *CustomerUsecase) Update(ctx context.Context, id string, input models.UpdateCustomerInput) (*models.Customer, TidyError) {
// Obtener los datos guardados
savedCustomer, err := u.Repo.FindOne(ctx, id)
if err.Err != nil {
return nil, TidyError{
Key: "uc_find_one_saved_customer_error",
Err: err,
}
}
// Fusionar los datos proporcionados con los datos guardados
updatedCustomer := utils.TransformMergeAndSet(input, savedCustomer,
func(c *models.Customer) {
c.IsActive = true
c.CustomField = "Nuevo valor"
},
)
// Guardar el registro actualizado en el repositorio
updatedCustomer, err = u.Repo.Update(ctx, id, updatedCustomer)
return updatedCustomer, TidyError{}
}
Funciones Anónimas para Modificación Dinámica
Una ventaja clave de este enfoque es que los setters pueden ser funciones anónimas. Esto permite realizar ajustes al vuelo sobre los campos de la entidad, sin necesidad de definir funciones auxiliares por separado.
updatedCustomer := utils.TransformMergeAndSet(input, savedCustomer,
func(c *models.Customer) {
c.IsActive = true
c.CustomField = "Nuevo valor"
},
)
Este patrón es especialmente útil cuando las modificaciones necesarias son específicas para un caso de uso y no se reutilizan en otros lugares.
Beneficios del Enfoque
- Reutilización del Código: Tanto
TransformAndSetcomoTransformMergeAndSetpermiten centralizar la lógica de transformación y actualización de datos, reduciendo la duplicación de código. - Flexibilidad Extrema: La capacidad de aplicar setters, ya sea definidos previamente o mediante funciones anónimas, proporciona un nivel de flexibilidad que se ajusta a los diferentes casos de uso, sin necesidad de modificar la lógica principal de creación o actualización.
- Adaptación a Cambios: El sistema es fácilmente extensible. Si los requisitos de negocio cambian y se requieren nuevas reglas para actualizar o crear entidades, basta con modificar o agregar setters, sin alterar la estructura base de las funciones.
- Simplicidad: Usar JSON como intermediario para transformar datos simplifica considerablemente el proceso de mapeo entre los tipos de entrada y los modelos de negocio. Esto es particularmente útil en sistemas donde los tipos de datos pueden ser similares pero no idénticos entre sí.
Conclusión
Las funciones TransformAndSet y TransformMergeAndSet representan un enfoque poderoso y flexible para gestionar la creación y actualización de entidades en el sistema TidyRocks. Al centralizar la lógica de transformación de datos y proporcionar la capacidad de aplicar setters personalizados, se logra un sistema que no solo es reutilizable y eficiente, sino también fácilmente adaptable a cambios futuros.
Este enfoque permite a los desarrolladores manejar la lógica de negocio de manera clara y mantener la coherencia a lo largo de todo el sistema, asegurando que las operaciones de creación y actualización se realicen de forma limpia y controlada.