DMarlon Studio

Lección 20 · 20 de 20

Proyecto: procesar pedidos · Go

Un proyecto que une lo aprendido en Go: pedidos en JSON, una función genérica que trabaja en paralelo con goroutines y un tipo con String.

Go · Proyecto: procesar pedidos en DMarlon Studio: el código del ejemplo y su salida en la terminal

El programa recibe pedidos en JSON, les suma el IVA en paralelo y los escribe. Es pequeño, pero junta cuatro piezas del curso: etiquetas JSON, genéricos, goroutines con canales e interfaces.

Order lleva etiquetas json:"id", json:"net" y json:"total", y `json.Unmarshal` rellena con ellas el slice orders a partir del texto. El error se escribe en la primera línea, <nil> si todo fue bien; en un programa real, si no fuera nil, se pararía ahí.

`ParallelMap[T, U any]` es genérica: sirve para cualquier slice y cualquier función. Lanza una goroutine por elemento y cada una escribe en su posición, out[i], así que el orden se conserva sea cual sea el que termine antes. Para saber cuándo han acabado todas, cada goroutine envía true por el canal done y la función recibe tantas señales como elementos: lo mismo que un WaitGroup, hecho con un canal.

La función que se le pasa es un literal, func(o Order) Order, que recibe una copia del pedido, le pone el Total (neto más el 21 %) y la devuelve. orders no se modifica y billed tiene los pedidos ya facturados. Go deduce solo que T y U son Order.

Para escribirlos basta con fmt.Println(o): Order tiene un método String, así que cumple `fmt.Stringer` y sale como #101 120.00 -> 145.20. Pasos naturales: IVA según el país, el resultado en JSON con json.Marshal(billed) o servirlo con un manejador HTTP, como hace el destino Web (Go) de DMarlon Studio.

Código · main.go

package mainimport (	"encoding/json"	"fmt")type Order struct {	ID    int     `json:"id"`	Net   float64 `json:"net"`	Total float64 `json:"total"`}func (o Order) String() string { return fmt.Sprintf("#%d %6.2f -> %6.2f", o.ID, o.Net, o.Total) }func ParallelMap[T, U any](in []T, f func(T) U) []U { // one goroutine per item, order kept	out, done := make([]U, len(in)), make(chan bool)	for i := range in {		go func(i int) { out[i] = f(in[i]); done <- true }(i)	}	for range in {		<-done // wait for one signal per goroutine	}	return out}func main() {	var orders []Order	err := json.Unmarshal([]byte(`[{"id":101,"net":120},{"id":102,"net":80},{"id":103,"net":45.5}]`), &orders)	fmt.Println("decoded", len(orders), "orders, error:", err)	billed := ParallelMap(orders, func(o Order) Order { o.Total = o.Net * 1.21; return o })	for _, o := range billed {		fmt.Println(o) // fmt finds the String method: Order is a fmt.Stringer	}}

Salida · go run main.go

decoded 3 orders, error: <nil>
#101 120.00 -> 145.20
#102  80.00 ->  96.80
#103  45.50 ->  55.05

Para recordar

  • Las etiquetas JSON enlazan las claves con los campos del struct.
  • Una función genérica con goroutines sirve para cualquier tipo.
  • Escribir en out[i] conserva el orden sin cerrojos.
  • Recibir una señal por goroutine equivale a esperar con un WaitGroup.
  • Un método String decide cómo se escribe el tipo.

Ejercicio

Añada a Order un campo Country (json:"country") y un mapa con el IVA de ES, DE y FR, ponga el país en cada pedido del JSON y use el IVA que corresponda. Al final, escriba billed en JSON con json.Marshal.