Skip to content

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

  1. You capture repeated traversal shapes once.
  2. You expose them with domain names such as Destinations or WhereOpen.
  3. You compose those methods to build readable, type-safe queries.

Query surfaces

  1. IGremlinQuerySource: domain entry points (for example, start from airport code).
  2. IVertexGremlinQuery<T>: vertex-to-vertex traversal and filtering logic.
  3. 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

  1. AirportWithCode: start from a single airport code without repeating V<Airport>().Where(...).
  2. Neighbours: traverse to connected airports via incoming or outgoing routes.
  3. Destinations: traverse to airports reachable by outgoing routes.
  4. Origins: traverse to airports that have an outgoing route to the current airport.
  5. WhereInCountry: filter airports by country.
  6. WhereOpen: filter airports by open status.
  7. WhereAnyNeighbour: keep airports where at least one neighbour satisfies a predicate.
  8. WithMinimumDistance: filter routes by a minimum distance threshold.

Usage examples

Find all neighbors of an airport

C#
var neighbourAirports = await source
    .AirportWithCode("JFK")
    .Neighbours()
    .ToArrayAsync();

Find direct flight destinations

C#
var flightDestinations = await source
    .AirportWithCode("JFK")
    .Destinations()
    .ToArrayAsync();

Find which airports can reach a given airport

C#
var originAirports = await source
    .AirportWithCode("LHR")
    .Origins()
    .ToArrayAsync();

Find long-distance routes

C#
var longRoutes = await source
    .E<Route>()
    .WithMinimumDistance(5000)
    .ToArrayAsync();

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

  1. Keep each DSL method focused on one domain concept.
  2. Compose small methods instead of hiding too much logic in one method.
  3. Use domain names, not technical helper names.
  4. Return query types so downstream methods stay chainable.
  5. Document expensive traversal patterns when they are non-obvious.

A good DSL makes Gremlinq queries read like domain behavior instead of traversal plumbing.