Gabriel Bernardo Paredes AbreuDecisiones de diseño, manejo de I/O pesado de binarios y el cálculo de algoritmos para...
Llega un punto en la trayectoria de todo ingeniero backend donde nos preguntamos: ¿Todo en la web se resume a guardar y extraer datos de una base de datos? Afortunadamente, no. Las aplicaciones operativas serias requieren procesos heavy-computation.
Recientemente, desarrollé un Sistema de Gestión Documental corporativo.
El dominio requería que los usuarios estructuraran árboles jerárquicos de información (Documento → Múltiples Secciones → Múltiples Insumos/PDFs preexistentes). La "magia" residía en que el sistema, a nivel de backend, debía calcular el peso, generar índices visuales dinámicos, compaginar físicamente e inyectar numeración a cientos de páginas en un solo stream. Acá expongo sobre cómo Go facilitó el proceso.
Construir una maraña de mergeos secuenciales hubiese reventado el consumo de memoria I/O y agotado el File System. Dividí la carga en tres componentes fundamentales:
Validar -> Limpiar -> Calcular Offsets -> Renderizar Índice Visual -> Merge -> Estampar Numeración.
Elegí fervientemente Go por sus superpoderes concurrentes, su excelente latencia en acceso al disco y su binario ejecutable predecible. En segundo lugar, quité toda referencia de librerías PDF dentro de la capa del Dominio; si el día de mañana deseamos llevar la creación a un Microservicio o Cola de Eventos (Event-Driven), el paquete Motor se migraría sin cambiar una sola línea de lógica comercial.
Para mantener el bajo acoplamiento, creamos un planificador algorítmico. Su tarea es barrer la colección inyectando hojas virtuales si el comportamiento comercial así lo requiere, mucho antes de tocar siquiera los módulos pesado de combinación (Merge).
package document_engine
import "fmt"
// PDFNode representa una estructura simplificada de nuestra jerarquía de negocio
type PDFNode struct {
Name string
TotalPages int
RequiresRightAlignment bool // Regla comercial: Si debe imprimirse a la derecha (Página impar)
InjectBlankPage bool // Bandera de buffer virtual
CalculatedOffset int // Cuál será su página física de arranque real
}
// CalculatePagination offsets procesa el árbol y devuelve el recuento matemático
func CalculatePaginationOffsets(nodes []PDFNode) int {
currentPage := 1 // En la realidad de impresión, siempre arrancamos en 1
for i := range nodes {
// Validamos si la regla de negocio física choca con la página actual
if nodes[i].RequiresRightAlignment && currentPage%2 == 0 {
fmt.Printf("-> [Lógica] Colisión par. Inyectando buffer en blanco previo a '%s'\n", nodes[i].Name)
// Mutamos el estado y forzamos a una página impar para resolver el choque
nodes[i].InjectBlankPage = true
currentPage++
}
// Asignamos el offset real de dónde empezará este componente en el reporte master
nodes[i].CalculatedOffset = currentPage
// Sumamos su peso para el siguiente nodo del ciclo
currentPage += nodes[i].TotalPages
}
totalAssignedPages := currentPage - 1
return totalAssignedPages
}
func main() {
// Ejemplo de simulación
nodes := []PDFNode{
{Name: "Portada", TotalPages: 1, RequiresRightAlignment: false},
{Name: "Capitulo_1_Intro", TotalPages: 2, RequiresRightAlignment: true}, // Arrancará en 3, salta la 2
{Name: "Capitulo_2_Data", TotalPages: 5, RequiresRightAlignment: true}, // Arrancará en 5
}
total := CalculatePaginationOffsets(nodes)
fmt.Printf("Total de planchas lógicas estimadas: %d\n", total)
}