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

> Learn how GAspect enables Aspect-Oriented Programming in CGraph. Easily inject logging, monitoring, and error handling into GElement lifecycles without core code changes.

- Repository: [Chunel/cgraph](https://github.com/chunelfeng/cgraph)
- Tags: how-to-guide
- Published: 2026-02-27

---

**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`](https://github.com/chunelfeng/cgraph/blob/main/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`](https://github.com/chunelfeng/cgraph/blob/main/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`](https://github.com/chunelfeng/cgraph/blob/main/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`](https://github.com/chunelfeng/cgraph/blob/main/tutorial/MyGAspect/MyTimerAspect.h):

```cpp
#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`](https://github.com/chunelfeng/cgraph/blob/main/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:

```cpp
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`](https://github.com/chunelfeng/cgraph/blob/main/src/GraphCtrl/GraphParam/GPassedParam.h)) and pass it during registration:

```cpp
// 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`](https://github.com/chunelfeng/cgraph/blob/main/python/wrapper/PywGAspect.h). You can implement cross-cutting concerns in Python while retaining the same lifecycle hooks:

```python
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`](https://github.com/chunelfeng/cgraph/blob/main/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.