Mesh Programming: Where Visual Design Meets Synchronized Development

Last week, our team shipped a complex event-driven search system that processes millions of bookings. What’s interesting isn’t just what we built, but how we built it. Today, I want to share a development approach we’ve been refining over the past few months – we call it Mesh Programming.

The Challenge

Picture this: You need to build a system that handles millions of booking records, needs to stay in sync with a source of truth via Kafka, and must provide lightning-fast search capabilities while protecting your upstream services. Sound familiar?

We faced this exact challenge when building our new Booking Search API. The requirements were clear: consume booking changes from Kafka, fetch complete data from our GraphQL service, maintain a search-optimized PostgreSQL database, and serve up booking IDs that match complex search criteria. All while ensuring our sensitive booking GraphQL service doesn’t get hammered with unnecessary requests.

Enter Mesh Programming

Mesh Programming grew organically from our team’s frustration with traditional approaches. It’s not revolutionary – it’s evolutionary. It takes the best parts of visual system design, component-based development, and synchronized integration (parts of trunk based development too), weaving them together into something that just works.

Here’s how we built our Booking Search API using this approach.

The Morning Mesh

Every significant piece of work starts with our team gathered around a whiteboard. No laptops, no phones – just markers, post-its, and ideas. We draw out the system, marking clear boundaries between components. Each post-it represents work that needs to be done.

For our booking system, this visual blueprint became our source of truth. It showed the flow from Kafka through to our search API, with clear interface boundaries that would let us work in parallel.

The Foundation

Once we had our visual blueprint, we kicked off with something I consider crucial – establishing our interfaces. In C#, this looked something like this:

public record SearchCriteria(
    DateTimeOffset? StartDate,
    DateTimeOffset? EndDate,
    List<int>? HotelIds,
    List<int>? PaymentTypeIds,
    List<int>? RoomTypeIds,
    BookingStatus? Status
);

public interface IBookingSearchRepository
{
    Task<IReadOnlyList<string>> FindBookingIdsAsync(
        SearchCriteria criteria, 
        CancellationToken cancellationToken
    );
    Task SaveBookingAsync(
        BookingData booking, 
        CancellationToken cancellationToken
    );
}

public interface IBookingGraphQLClient
{
    Task<BookingData> GetBookingDetailsAsync(
        string bookingId, 
        CancellationToken cancellationToken
    );
}

These interfaces became our contract. With them in place, our team could split up and work in parallel, confident that our components would fit together when we regrouped.

Now, a very important detail here. Everyone works on the SAME BRANCH, this is the trunk based development part. It doesn’t and probably shouldn’t be master. But it’s important and that you push often to integrate often for the collaboration to work effectively. Don’t create PRs to each other branches, or try to merge between branches, it slows down your integration and makes for more merge conflicts. Need to review some code in a diff? use the commit diff. What about commenting on it, go talk to the person, make it synchronous, you are NOT a globally distributed team that works on the Linux kernel, you are a Product Engineering Team.

The Dance of Parallel Development

The beauty of Mesh Programming reveals itself when you start seeing multiple components take shape simultaneously. While one pair worked on the PostgreSQL repository:

public sealed class PostgresBookingRepository : IBookingSearchRepository
{
    private readonly NpgsqlDataSource _dataSource;

    public PostgresBookingRepository(NpgsqlDataSource dataSource) => 
        _dataSource = dataSource;

    public async Task<IReadOnlyList<string>> FindBookingIdsAsync(
        SearchCriteria criteria, 
        CancellationToken cancellationToken)
    {
        await using var command = _dataSource.CreateCommand(
            """
            SELECT booking_id 
            FROM booking_search 
            WHERE (@StartDate IS NULL OR start_date >= @StartDate)
              AND (@EndDate IS NULL OR end_date <= @EndDate)
              ...
            ORDER BY start_date DESC
            """
        );
        // Parameter setup and execution...
        return await command.ExecuteReaderAsync()
            .ToListAsync(row => row.GetString(0), cancellationToken);
    }
    public async Task SaveBookingAsync(
        BookingData booking, 
        CancellationToken cancellationToken
    )
    {
        //...
    }
}

Another pair was busy with the Kafka consumer.

And others working on the integration test in parallel too.

The Integration Dance

What makes Mesh Programming unique is its rhythm of integration. We sync multiple times a day an example might be morning, noon, and afternoon, this varies based on the work. These aren’t a standup, don’t confuse them, and you wont need them if you follow this because you are talking all day long. They’re active integration sessions where we:

  1. Share interface evolution
  2. Pair on integration points
  3. Run our comprehensive integration test(s), and try to make it pass!

Speaking of integration tests, we believe in testing the real thing. Using Testcontainers for .NET along with WebApplicationFactory to start a web server to test on, example below, its easy to write a black box high level test that tells us if we have the system working end 2 end.

[TestFixture]
public sealed class BookingSearchIntegrationTests : IAsyncLifetime
{
    private readonly PostgreSqlContainer _postgres = new();
    private readonly KafkaContainer _kafka = new();
    private readonly Mock<IBookingGraphQLClient> _graphQLClient = new();
    private WebApplicationFactory<Program> _factory;

    public async Task InitializeAsync()
    {
        await Task.WhenAll(
            _postgres.StartAsync(),
            _kafka.StartAsync()
        );

         _factory = new WebApplicationFactory<Program>()
            .WithWebHostBuilder(builder =>
            {
                builder.ConfigureServices(services =>
                {
                    // you can override DI here for database and kafka config
                    services.Configure<DbConfiguration>(options => { options.ConnectionString = _postgres.GetConnectionString(); });
                });
            });
    }

    [Test]
    public async Task WhenBookingEventReceived_ThenBookingBecomesSearchable()
    {
        // Arrange
        var bookingId = Guid.NewGuid().ToString();
        var bookingEvent = new BookingEvent(bookingId, EventType.Created);
        
        _graphQLClient
            .Setup(x => x.GetBookingDetailsAsync(bookingId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(new BookingData(/* ... */));

        // Act
        await _kafka.ProduceMessageAsync("booking-events", bookingEvent);
        
        // Assert
        await AssertEventuallyAsync(async () =>
        {
            var results = await _searchRepository.FindBookingIdsAsync(
                new(StartDate: null, EndDate: null, Location: null, Status: null),
                new(Page: 1, PageSize: 10),
                CancellationToken.None
            );
            
            Assert.Contains(bookingId, results);
        });
    }
}

Why It Works

Mesh Programming isn’t just another methodology – it’s a recognition of how modern teams actually work best. The visual aspect ensures everyone shares the same mental model. The interface-first approach enables true parallel development. And the regular integration points keep everyone in sync without the overhead of excessive meetings.

For our Booking Search API, this approach meant we could go from whiteboard to production in a fraction of the time it would have taken with traditional approaches. The system handles millions of bookings, stays perfectly in sync, and most importantly, was a joy to build.

Looking Forward

We’re still refining Mesh Programming, finding new patterns and anti-patterns. But one thing’s clear – this approach has transformed how our team builds software. It’s made us faster, more collaborative, and dare I say it, happier developers.

In future posts, I’ll dive deeper into specific aspects of Mesh Programming – from visual design patterns to integration strategies. But for now, I’d love to hear your thoughts. Have you tried similar approaches? What works in your team?

Until next time, happy coding!

Leave a comment