How to Use GAspect for AOP in CGraph to Manage Cross-Cutting Concerns

GAspect provides a lightweight Aspect-Oriented Programming (AOP) mechanism in CGraph that intercepts GElement lifecycle events through hook methods like beginRun and finishRun, allowing you to inject logging, monitoring, and error handling without modifying core business logic.

The CGraph repository implements a flexible AOP framework through the GAspect hierarchy. By attaching aspect objects to graph elements or entire pipelines, you can manage cross-cutting concerns—such as performance tracing, resource monitoring, and exception handling—in a modular way that keeps your core computation logic clean and reusable.

Understanding the GAspect Architecture

The AOP implementation in CGraph follows a layered design with clear separation between interface definition, object management, and runtime execution.

Core Interface and Lifecycle Hooks

The GAspect class in src/GraphCtrl/GraphAspect/GAspect.h defines the pure virtual interface that all aspects must implement. It exposes hook methods that intercept three major lifecycle phases—initialization, execution, and destruction—plus error handling events:

  • beginInit() / finishInit() – Wrap element initialization
  • beginRun() / finishRun() – Wrap the core execution logic
  • beginDestroy() / finishDestroy() – Wrap resource cleanup
  • enterCrashed() / enterTimeout() – Handle error states

Concrete aspects inherit from GAspect and override only the hooks relevant to their specific cross-cutting concern.

Aspect Parameters and Base Objects

The GAspectObject class in src/GraphCtrl/GraphAspect/GAspectObject.h serves as the base for all aspect instances. It manages the optional aspect parameter (GAspectParamPtr) through getAParam<T>() and setAParam() methods, allowing you to pass configuration data—such as logging levels or timeout thresholds—to individual aspect instances without global variables.

Notably, GAspectObject hides the run() method because aspects do not participate in the normal graph execution flow; they only observe and wrap it.

Centralized Aspect Management

The GAspectManager in src/GraphCtrl/GraphAspect/GAspectManager.h maintains a std::vector<GAspectPtr> registry for each pipeline. It provides the reflect method that the scheduler calls to invoke the appropriate hooks on all registered aspects at lifecycle boundaries. This centralized dispatch ensures that cross-cutting concerns execute consistently across all attached elements.

Implementing Cross-Cutting Concerns with GAspect

Creating and attaching aspects requires subclassing GAspect, implementing relevant hooks, and registering the aspect via the GPipeline API.

Creating a Custom Timer Aspect

The following example implements a performance monitoring aspect that measures execution duration. This pattern appears in the tutorial files at tutorial/MyGAspect/MyTimerAspect.h:

#include "CGraph.h"
#include <chrono>

class MyTimerAspect : public CGraph::GAspect {
public:
    MyTimerAspect() = default;
    ~MyTimerAspect() override = default;

    CStatus beginRun() override {
        start_ = std::chrono::steady_clock::now();
        CGraph::CGRAPH_ECHO("[MyTimerAspect] %s - run start",
                            this->getName().c_str());
        return CStatus();
    }

    CVoid finishRun(const CStatus& curStatus) override {
        auto end = std::chrono::steady_clock::now();
        auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(end - start_);
        CGraph::CGRAPH_ECHO("[MyTimerAspect] %s - run finished, cost %lld ms, code=%d",
                            this->getName().c_str(),
                            static_cast<long long>(dur.count()),
                            curStatus.getCode());
    }

private:
    std::chrono::steady_clock::time_point start_;
};

Attaching Aspects to Pipelines and Elements

The GPipeline class in src/GraphCtrl/GraphPipeline/GPipeline.h exposes the addGAspect<TAspect, TParam>() template method (lines 83-88) to register aspects. You can scope an aspect to the entire pipeline or to specific elements:

CGraph graph;
auto pipeline = graph.createPipeline();

// Attach to all elements in the pipeline
pipeline->addGAspect<MyTimerAspect>();

// Or scope to specific elements only
std::unordered_set<CGraph::GElementPtr> targetElems = { myNodePtr, myClusterPtr };
pipeline->addGAspect<MyTimerAspect>(targetElems);

Passing Configuration via GAspectParam

For reusable aspects that require configuration, define a parameter class inheriting from GAspectParam (typedef of GPassedParam in src/GraphCtrl/GraphParam/GPassedParam.h) and pass it during registration:

// Define configuration structure
class MyTimerParam : public CGraph::GAspectParam {
public:
    int warnThresholdMs = 100;  // Log warning if execution exceeds this
};

// Register with parameter
auto param = std::make_shared<MyTimerParam>();
pipeline->addGAspect<MyTimerAspect>(targetElems, param.get());

// Access inside the aspect
CStatus beginRun() override {
    if (auto p = this->getAParam<MyTimerParam>()) {
        if (p->warnThresholdMs > 0) {
            // Setup warning logic
        }
    }
    return CStatus();
}

Python Support for GAspect

CGraph exposes the aspect system to Python through PywGAspect in python/wrapper/PywGAspect.h. You can implement cross-cutting concerns in Python while retaining the same lifecycle hooks:

import cgraph

class PyTraceAspect(cgraph.PywGAspect):
    def beginRun(self):
        cgraph.CGRAPH_ECHO(">>> Python Trace: %s beginRun" % self.getName())
        return cgraph.CStatus()

    def finishRun(self, status):
        cgraph.CGRAPH_ECHO(">>> Python Trace: %s finishRun, code=%d" %
                           (self.getName(), status.getCode()))

# Usage

graph = cgraph.CGraph()
pipeline = graph.createPipeline()
pipeline.addGAspect(PyTraceAspect())

Summary

  • GAspect provides a native AOP framework in CGraph that intercepts GElement lifecycle events through virtual hooks like beginRun and finishRun.
  • The architecture separates concerns into GAspect (interface), GAspectObject (parameter handling), and GAspectManager (dispatch), located in src/GraphCtrl/GraphAspect/.
  • Attach aspects via GPipeline::addGAspect<T>() to inject logging, timing, or error handling without modifying core element logic.
  • Pass configuration data through GAspectParam subclasses using getAParam<T>() inside hook implementations.
  • Python users can implement aspects by inheriting from PywGAspect with identical lifecycle interception capabilities.

Frequently Asked Questions

What is the difference between GAspect and a normal GElement in CGraph?

A GElement represents a computational node that executes business logic through its run() method, while a GAspect is a non-computational observer that wraps around an element's lifecycle. Aspects do not have their own run() implementation—instead, they provide beginRun() and finishRun() hooks that execute immediately before and after the element's actual execution. This distinction allows aspects to handle cross-cutting concerns like logging and monitoring without interfering with the graph's computational flow.

Can I attach multiple GAspect instances to a single GElement?

Yes, the GAspectManager stores aspects in a std::vector<GAspectPtr>, allowing multiple aspects to be attached to the same element or pipeline. When a lifecycle event occurs, the manager iterates through all registered aspects and invokes the corresponding hook on each one. This composability enables you to layer concerns—for example, combining a timing aspect with a logging aspect—without creating conflicts or dependencies between them.

How do I handle errors inside a GAspect hook method?

Aspect hooks return CStatus objects that propagate through the execution chain. If a beginRun() hook returns a non-OK status, the framework can prevent the underlying element from executing. Similarly, finishRun() receives the curStatus parameter containing the result of the element's execution, allowing the aspect to inspect error codes and react accordingly—such as logging crash details via enterCrashed() or timeout events via enterTimeout(). Always check curStatus.isOK() in finish hooks to determine if the element succeeded.

Is it possible to use GAspect with the Python bindings in CGraph?

Yes, CGraph exposes the aspect system to Python through the PywGAspect class defined in python/wrapper/PywGAspect.h. You can create a Python class that inherits from cgraph.PywGAspect and overrides methods like beginRun() and finishRun(). These Python aspects integrate seamlessly with C++ pipelines, allowing you to implement cross-cutting concerns in Python while maintaining the performance of the underlying C++ graph execution engine.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →