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 initializationbeginRun()/finishRun()– Wrap the core execution logicbeginDestroy()/finishDestroy()– Wrap resource cleanupenterCrashed()/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
GElementlifecycle events through virtual hooks likebeginRunandfinishRun. - The architecture separates concerns into
GAspect(interface),GAspectObject(parameter handling), andGAspectManager(dispatch), located insrc/GraphCtrl/GraphAspect/. - Attach aspects via
GPipeline::addGAspect<T>()to inject logging, timing, or error handling without modifying core element logic. - Pass configuration data through
GAspectParamsubclasses usinggetAParam<T>()inside hook implementations. - Python users can implement aspects by inheriting from
PywGAspectwith 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →