¡Hola, mundo!
Empecemos con una Action que no hace nada útil: imprime una línea y devuelve un stub vacío. Su valor está en otra parte: muestra el mínimo de declaraciones que AOA exige antes de ejecutar absolutamente nada.
$ uv run python examples/step_01_Action_and_pipeline/01_hello_world.pySon bastantes líneas para un simple saludo, pero ninguna es un ritual: cada una declara algo sin lo cual una Action en AOA no se considera completa. Todo empieza con un domain. @meta es el pasaporte de la Action; @check_roles(GuestRole) declara el acceso de forma explícita, nunca por defecto. Y @summary_aspect es el único punto de salida: sáltate cualquiera de los tres y la máquina se niega a ejecutar la Action, incluso antes de la primera llamada.
Params, Result y box
Los stubs están bien para las introducciones; una Action real trabaja con datos. Démosle una entrada y una salida, y cambiemos print por el instrumento que usan las Actions en AOA para hablar con el mundo exterior.
$ uv run python examples/step_01_Action_and_pipeline/02_params_result_and_box.pybox es más interesante que print: es un logger estructurado ligado al paso actual, que lleva el canal, el nivel, el domain, la action y el nombre del aspecto, por lo que se presta al filtrado, el enrutamiento y la exportación. Adónde va el evento no lo decide la Action: es cosa de la máquina, conectada desde fuera.
Múltiples aspectos
Un solo paso rara vez es suficiente. AOA no deja libertad para que los datos intermedios se escondan en variables locales o campos de objetos: fluyen a través del pipeline en un state explícito, mientras que la propia Action permanece vacía entre llamadas.
$ uv run python examples/step_01_Action_and_pipeline/03_multiple_aspects.pyEl diccionario devuelto reemplaza state por completo — no lo extiende. @result_string("cleaned", required=True) es un verificador: un contrato verificable sobre la salida de un aspecto. Si se rompe la promesa, la máquina detiene el aspecto en el acto, sin dejar pasar nunca un state corrupto.
Herencia
Las Actions heredan como las clases normales, con una excepción deliberada: los aspectos no se heredan en el pipeline. La máquina construye el pipeline únicamente a partir de lo declarado en la propia clase.
$ uv run python examples/step_01_Action_and_pipeline/04_inheritance.pyChildOrderAction se ejecuta sin problemas, pero su pipeline solo contiene child_summary: el validate_aspect del padre nunca se ejecuta. ExtendedOrderAction lo hace bien: vuelve a declarar el aspecto y llama a super() para construir sobre la lógica del ancestro.
Experimentos
Este capítulo ha acumulado un buen número de reglas, pero todas comparten una propiedad: casi todas se verifican al declarar la clase, al importar el módulo, no al llamar a la Action. El código aquí es una especificación ejecutable; una violación del contrato sale a la luz antes de la primera ejecución, no una noche cualquiera en producción. Pruébalo: elige una forma de romper SayHelloAction del primer ejemplo y pulsa Ejecutar.
$ uv run python examples/step_01_Action_and_pipeline/01_hello_world.pyResumen
Ya tenemos el núcleo del modelo: una operación es una Action con un límite tipado (Params y Result), un pipeline lineal de aspectos y contratos que se verifican, en su mayoría, en la inicialización. El comportamiento está en la estructura del código, no en la documentación que lo describe por fuera.
Preguntas de repaso
- ¿Qué invariante garantiza la legibilidad «de una sola clase» de una Action, y en qué momento se verifica?
- ¿Por qué la ausencia de
@check_roleses un error y no un silencioso «abierto a todos»? - El pipeline de AOA es lineal: sin ramificaciones, sin salidas laterales. ¿Qué gana esta restricción, y a qué costo?
- ¿Por qué los aspectos no se heredan automáticamente en el pipeline? Compáralo con la herencia normal de métodos en la POO.
- ¿Qué significa que «el código es una especificación ejecutable», y por qué la mayoría de los contratos se verifican en la inicialización y no en tiempo de ejecución?
