Domain-Specific Languages (DSLs)
In Gremlinq, a DSL is your domain vocabulary expressed as extension methods over query types. Instead of repeating low-level traversal steps, you package them into names that match business intent.
What a DSL means in Gremlinq
- You capture repeated traversal shapes once.
- You expose them with domain names such as
DestinationsorWhereOpen. - You compose those methods to build readable, type-safe queries.
Query surfaces
IGremlinQuerySource: domain entry points (for example, start from airport code).IVertexGremlinQuery<T>: vertex-to-vertex traversal and filtering logic.IEdgeGremlinQuery<T>: edge-specific filtering and projection logic.
Extensions should return query types so composition remains fluent. For methods that accept a caller-supplied filter, use a typed predicate such as Func<IVertexGremlinQuery<Airport>, IGremlinQueryBase> and apply it inside Where(...).
AirRoutes DSL example
C#
public static class AirRoutesDslExtensions
{
public static IVertexGremlinQuery<Airport> AirportWithCode(this IGremlinQuerySource source, string code)
{
return source
.V<Airport>()
.Where(a => a.Code == code);
}
public static IVertexGremlinQuery<Airport> Neighbours(this IVertexGremlinQuery<Airport> query)
{
return query
.Union(
__ => __
.Out<Route>()
.OfType<Airport>(),
__ => __
.In<Route>()
.OfType<Airport>()
)
.Dedup();
}
public static IVertexGremlinQuery<Airport> Destinations(this IVertexGremlinQuery<Airport> query)
{
return query
.Out<Route>()
.OfType<Airport>();
}
public static IVertexGremlinQuery<Airport> Origins(this IVertexGremlinQuery<Airport> query)
{
return query
.In<Route>()
.OfType<Airport>();
}
public static IVertexGremlinQuery<Airport> WhereInCountry(this IVertexGremlinQuery<Airport> query, string country)
{
return query
.Where(a => a.Country == country);
}
public static IVertexGremlinQuery<Airport> WhereOpen(this IVertexGremlinQuery<Airport> query)
{
return query
.Where(a => a.Status == AirportStatus.Open);
}
public static IVertexGremlinQuery<Airport> WhereAnyNeighbour(
this IVertexGremlinQuery<Airport> query,
Func<IVertexGremlinQuery<Airport>, IGremlinQueryBase> predicate)
{
return query
.Where(__ => predicate(__.Neighbours()));
}
public static IEdgeGremlinQuery<Route> WithMinimumDistance(this IEdgeGremlinQuery<Route> query, long minDistance)
{
return query
.Where(r => r.Distance >= minDistance);
}
}
Method intent
AirportWithCode: start from a single airport code without repeatingV<Airport>().Where(...).Neighbours: traverse to connected airports via incoming or outgoing routes.Destinations: traverse to airports reachable by outgoing routes.Origins: traverse to airports that have an outgoing route to the current airport.WhereInCountry: filter airports by country.WhereOpen: filter airports by open status.WhereAnyNeighbour: keep airports where at least one neighbour satisfies a predicate.WithMinimumDistance: filter routes by a minimum distance threshold.
Usage examples
Find all neighbors of an airport
Find direct flight destinations
Find which airports can reach a given airport
Find long-distance routes
Compose multiple DSL methods for one domain query
C#
var openDomesticDestinations = await source
.AirportWithCode("SFO")
.Destinations()
.WhereInCountry("US")
.WhereOpen()
.ToArrayAsync();
Filter airports by neighbour predicate
C#
var airportsWithOpenDomesticNeighbours = await source
.V<Airport>()
.WhereAnyNeighbour(__ => __
.WhereInCountry("US")
.WhereOpen())
.ToArrayAsync();
Design guidelines
- Keep each DSL method focused on one domain concept.
- Compose small methods instead of hiding too much logic in one method.
- Use domain names, not technical helper names.
- Return query types so downstream methods stay chainable.
- Document expensive traversal patterns when they are non-obvious.
A good DSL makes Gremlinq queries read like domain behavior instead of traversal plumbing.