Type something to search...

Streaming Wedding Images Worldwide with Dapr and Lightstreamer

Streaming Wedding Images Worldwide with Dapr and Lightstreamer

Introduction

For years I have been obsessed with delivering live experiences across massive distances. Weddings, in particular, present a beautiful challenge: family members may be scattered across continents, yet everyone wants to feel like they are standing right beside the couple. With WedCam, my goal was to bridge that gap. Early prototypes showed that simply storing images in a cloud bucket and pushing them over HTTP was not enough. Latency killed the immediacy of the moment, and reliability suffered in locations with weaker connectivity. I needed a combination of real-time streaming and rock-solid distribution. That’s when I started looking into Dapr for microservice communication and Lightstreamer for event streaming. Together, they provided a powerful foundation. With Dapr’s sidecar pattern abstracting messaging and state management, I could concentrate on my application logic. Lightstreamer, known for its efficient push technology, ensured images could fly across the globe with minimal delay. The result is a seamless viewing experience for guests everywhere.

Dapr, or Distributed Application Runtime, is an open-source project designed to simplify microservice development. Rather than forcing developers to write plumbing code for every service, Dapr exposes building blocks through HTTP or gRPC. Each component—such as state storage, pub/sub messaging, or bindings—is run as a sidecar. Services call the sidecars over localhost, and Dapr handles the rest. In WedCam, I rely heavily on Dapr’s pub/sub building block to ferry image events between services. When a new photo is snapped, the camera service publishes a message. Because each sidecar abstracts the underlying broker, I can swap from Kafka to Redis to RabbitMQ without rewriting business logic. Another valuable feature is the outbox pattern, which allows me to write to a database and send messages atomically. By configuring Dapr’s bindings to act as the outbox, I ensure a photo is stored before notifying the stream that it is ready for distribution. This guards against lost images even in failure scenarios.

The outbox pattern has long been a reliable technique for ensuring data consistency between transactional systems and asynchronous queues. Traditionally, you’d write an entry in a database table to record an event, then rely on a background job to read that table and publish messages. Dapr streamlines this approach. In WedCam, when a photo is saved to our storage service, a Dapr binding also writes an event record to a durable outbox store. The sidecar ensures both operations either succeed or fail as a unit. Later, Dapr’s pub/sub component reads those records and publishes them onto the messaging system. This abstraction means I don’t need to implement a custom polling mechanism or worry about race conditions. If a failure occurs, Dapr’s retry policies kick in automatically. This pattern is especially crucial when we have multiple streaming nodes; every image must be replicated across them reliably. With Dapr, the complexity of the outbox is hidden away, yet the guarantees remain.

So how does Lightstreamer fit in? Lightstreamer is a high-performance server built for pushing data to clients with minimal overhead. It supports a wide range of protocols and can deliver updates to browsers, mobile apps, and even IoT devices. In WedCam, I run Lightstreamer as a dedicated service alongside my other microservices. Each microservice communicates with its Dapr sidecar, while Lightstreamer uses its own adaptors to ingest events. When a camera publishes a new image event through Dapr, the sidecar forwards it to a queue. A small Lightstreamer adaptor subscribes to that queue and pushes the new image to connected clients in real time. Because Dapr hides the specifics of the underlying messaging system, I can change the broker or adjust quality-of-service settings without rewriting the Lightstreamer integration. This decoupling means I can tune delivery for different regions, experiment with various back-end configurations, and maintain a straightforward Lightstreamer adaptor.

CDNs, or Content Delivery Networks, are another cornerstone of WedCam’s architecture. Images can be sizable, and distributing them from a single region adds latency. By pushing images to a CDN, we ensure that guests around the world pull them from edge nodes close to their location. Dapr helps here as well. After the image event is processed, a worker service retrieves the image, optimizes it, and uploads it to our CDN provider. Because the worker uses Dapr’s service invocation building block, the call is made to the sidecar via localhost, while the sidecar handles authentication and retries. Once the CDN has the image, Lightstreamer notifies clients to fetch it from the nearest edge server. This combination of streaming updates and global caching delivers an experience where photos appear instantly, without the stuttering or delays typical of heavy payloads. Guests can open their phone or laptop and see new pictures as soon as they are captured.

Every wedding is unique, so WedCam supports custom settings for each event. Couples might want certain photos public and others restricted to close friends. Some prefer faster updates in exchange for lower resolution, while others choose the highest quality even if it takes a few more milliseconds. Dapr’s configuration building block allows me to store these settings centrally and inject them into services at runtime. For example, the camera service queries Dapr for the event’s resolution preferences, while the streaming service asks for privacy controls. Lightstreamer uses these settings to manage which clients are allowed to subscribe to which channels. If the couple updates their preferences, Dapr propagates the changes without redeploying or restarting the services. This dynamic configuration ensures that WedCam remains flexible. No two weddings follow the same script, and the technology needs to adapt seamlessly to each celebration’s mood and requirements.

From an architectural standpoint, WedCam consists of several microservices deployed as containers. Each one has its own Dapr sidecar. The capture service handles the camera interface, the processing service performs image optimization, the streaming service pushes updates to Lightstreamer, and a configuration service houses event-specific settings. A storage service persists images in an object store, while a CDN worker syncs those images to the global edge. Because Dapr sidecars communicate over localhost, the services remain language-agnostic. I can write one component in Go, another in C#, and yet another in Node.js, all without worrying about each technology’s specific messaging or persistence client. The sidecars rely on a common set of components defined in Dapr’s configuration. If we decide to move from Redis to NATS for messaging, we simply update the component YAML and redeploy. Dapr manages the connection details and ensures our services keep communicating smoothly.

Lightstreamer itself remains fairly lean. I configure an adaptor that listens on a Dapr pub/sub topic. Whenever an image event arrives, the adaptor loads metadata from the Dapr state store, retrieves the image from our CDN, and streams it to connected clients. Lightstreamer uses a publish-subscribe model with real-time updates, so each client sees new pictures pop up almost instantly. Because the adaptor is small, it can be swapped out easily to support new protocols or handle additional processing. For instance, we plan to implement a feature that automatically adjusts image orientation based on the device’s position. By adding a transformation step in the Lightstreamer adaptor, I can roll out that capability with minimal impact on other services. Again, Dapr’s abstraction of the underlying infrastructure keeps the integration straightforward and flexible.

Streaming photos across the world is only half the story. Weddings often have multiple photographers capturing the same event from different angles. WedCam supports ingesting images from all of them simultaneously. Each photographer’s camera sends images to its own capture service instance, but because they all communicate through Dapr’s pub/sub building block, the downstream pipeline sees a unified stream of events. The storage service writes each photo, tagging it with metadata such as the photographer ID and timestamp. Lightstreamer groups images into separate channels so viewers can choose to follow an individual photographer or the combined feed. This multi-source approach also provides redundancy: if one camera drops off due to network issues, the others continue delivering content. Dapr’s retry and back-off mechanisms help recover when connectivity returns. The result is a comprehensive and resilient view of the ceremony that truly captures every moment.

Handling unreliable connections is a critical part of WedCam’s design. Some wedding venues may have spotty Wi-Fi or limited cellular coverage. Dapr helps mitigate this through its resiliency policies. I can configure retries with exponential back-off, circuit breakers, and timeouts at the sidecar level. When a capture service cannot reach the pub/sub broker or state store, it doesn’t simply fail; Dapr automatically retries and, if necessary, persists the event locally until the connection is restored. Lightstreamer, in turn, has built-in buffering that keeps clients synced once they reconnect. These features combine to provide a graceful degradation instead of abrupt failures. Guests might see a brief delay, but they won’t miss an important moment. By testing under simulated low-bandwidth conditions, I’ve tuned the Dapr and Lightstreamer settings to deliver the best possible experience even when the network is less than ideal.

Security plays a major role in any system that handles personal memories. Wedding images are sensitive, and privacy is paramount. Dapr includes built-in support for secure secrets storage. Instead of embedding credentials directly in configuration files, I store API keys and connection strings in a secure vault. Dapr sidecars retrieve them on startup and expose them only to the relevant service. Communication between services is encrypted using mutual TLS, which Dapr manages under the hood. Lightstreamer connections are also secured via HTTPS and WebSockets with TLS. For additional protection, I integrated token-based authentication at the application layer. When guests join an event, they receive a short-lived token that grants access to the specific image streams they should see. This token is validated by the Lightstreamer adaptor, ensuring unauthorized users cannot tap into the feed. By combining Dapr’s secret management with Lightstreamer’s encrypted channels, WedCam keeps wedding memories safe.

Another benefit of using Dapr and Lightstreamer is their scalability. Weddings vary greatly in size—from small gatherings of twenty guests to massive celebrations streamed to thousands of remote viewers. Dapr makes scaling services trivial because each sidecar is stateless. I can replicate the capture service or the streaming service across multiple pods in Kubernetes, and Dapr handles load balancing across them. Lightstreamer has a robust clustering model that distributes client connections across multiple nodes. As traffic grows, I spin up additional Lightstreamer servers, connect them to the same pub/sub topics, and rely on the CDN to serve the heavier image payloads. This elastic scaling ensures we maintain low latency regardless of the audience size. During peak viewing hours, we might have hundreds of concurrent viewers. At quieter times, we can scale down to save costs. Because configuration is centralized and runtime communication uses sidecars, I can make scaling decisions without rewriting the codebase.

Cost efficiency is important for both our team and the couples who choose WedCam. Running multiple streaming servers and maintaining global CDN distribution could easily become expensive. Dapr enables a pay-for-use model by letting services scale independently. For example, the processing service only needs to run when there are new images, so we scale it down to zero during idle times. Dapr’s event-driven model wakes it up whenever a new message arrives. Lightstreamer licenses can also be scaled dynamically. We monitor audience size and spin up new instances only when necessary, then scale them back down after the event ends. The CDN costs are offset by caching images near users, meaning we pay primarily for egress traffic when someone actually views the photos. By combining Dapr’s service autonomy with Lightstreamer’s efficient streaming and the global reach of CDNs, WedCam delivers a premium experience while keeping operational costs in check.

A big part of what makes WedCam special is the ability to tailor the viewing experience. Couples can choose themed overlays, custom transitions, and personalized slideshows. These features run as microservices that modify images on the fly. Dapr’s service invocation building block keeps the coordination simple. The streaming service calls the overlay service through the sidecar, sending the latest image ID. The overlay service retrieves the image from the CDN, adds the theme elements, writes it back to storage, and publishes a new event via the outbox pattern. Lightstreamer then notifies clients that the updated image is available. Because each service uses the same sidecar to communicate, they share the same authentication and retry logic. This modular approach allows us to develop new features rapidly. When a couple requests a new overlay style, I can implement it as a new microservice without altering the existing pipeline. Dapr ensures everything stays connected.

Another unique requirement is dealing with guests in vastly different time zones. Some might join live, while others may watch highlights later. Using Dapr, I store timestamps and metadata in a state store that Lightstreamer references when constructing playback sessions. Guests who cannot watch the ceremony live can still stream the images in order later, almost like a time-shifted video. Dapr ensures that the same outbox events used to deliver real-time images also persist the metadata needed for playback. This dual-purpose design avoids duplication and keeps the system simple. Additionally, because everything is timestamped and stored via Dapr components, I can run analytics to determine viewer engagement around the world. This data helps optimize CDN placement and Lightstreamer server locations, ensuring we provide the lowest latency possible no matter where the guests are watching from.

Disasters can strike at any time, even during a joyful wedding ceremony. To guard against outages, I run WedCam across multiple cloud regions. Dapr’s support for multi-datacenter deployments allows each sidecar to communicate with local components first, then fail over if necessary. Lightstreamer also supports clustering across regions, with clients automatically reconnecting to the nearest healthy server. When an entire region goes down, Dapr’s placement service reroutes messages to a backup broker in another region. Because the CDN already distributes images globally, the only effect is a brief failover delay. This architecture proved invaluable during a test event when our primary cloud provider experienced network issues. The Dapr sidecars shifted to the secondary region within seconds, and Lightstreamer connections recovered automatically. Guests barely noticed the hiccup, and the ceremony continued without any lost photos. Building this level of resilience would have been much harder without Dapr’s abstractions.

While reliability in production is crucial, developer experience is equally important. Dapr’s local development features allow me to run most of WedCam on my laptop. Each service runs with its sidecar, which communicates with a lightweight set of local components—file-based state stores, an in-memory pub/sub broker, and stubs for the CDN. Lightstreamer can also run in a local mode, making it easy to test changes before deploying them to the cloud. Because the configuration files are the same across environments, I know that what works on my machine will behave consistently in production. This local workflow accelerates feature development. I can try out new overlay effects, adjust outbox settings, or refine Dapr resiliency policies without waiting for a CI/CD pipeline. Once I’m satisfied, I commit the changes and let our build system deploy to staging and then to production, confident that the sidecars will behave exactly the same way.

Service invocation is another Dapr capability that simplifies communication between microservices. Instead of hard-coding hostnames and ports, each service simply calls its sidecar with the name of the target service. Dapr handles service discovery and, if needed, mutual TLS encryption. In WedCam, the processing service invokes the CDN worker to upload optimized images. The streaming service invokes the analytics service to log view counts. Because the calling code is just a REST or gRPC request to localhost, I can change the service location without touching the client code. This also plays nicely with container orchestrators like Kubernetes. During rolling updates, Dapr ensures that requests continue to find the correct service instances, smoothing out the deployment process. It’s particularly helpful for the overlay microservices, which might be deployed or scaled at different times from the core capture and streaming services.

Below is a simplified example of how the outbox pattern works in WedCam using Dapr. The capture service writes an image record to the database and publishes an event in one transaction. Dapr’s bindings handle the atomicity:

[ApiController]
[Route("api/[controller]")]
public class CaptureController : ControllerBase
{
    private readonly DaprClient _dapr;
    private readonly ImageDbContext _db;

    public CaptureController(DaprClient dapr, ImageDbContext db)
    {
        _dapr = dapr;
        _db = db;
    }

    [HttpPost]
    public async Task<IActionResult> UploadAsync([FromBody] ImageUpload request)
    {
        var entity = new ImageEntity
        {
            Id = Guid.NewGuid(),
            EventId = request.EventId,
            Timestamp = DateTime.UtcNow
        };
        _db.Images.Add(entity);
        await _db.SaveChangesAsync();

        await _dapr.InvokeBindingAsync("outbox", "create", entity);
        return Ok(entity.Id);
    }
}

In this snippet, the image metadata is stored in the database via Entity Framework. Immediately afterward, Dapr’s binding component writes to the outbox. From there, a Dapr pub/sub component reads the outbox and publishes the event to Lightstreamer. The binding ensures that if the database write fails, no message is sent, preserving consistency.

On the receiving side, the Lightstreamer adaptor subscribes to the pub/sub topic and forwards images to clients. Because Dapr decodes the message into the original entity, the adaptor code stays lightweight. In C#, it might look like this:

public class ImageStreamAdapter
{
    private readonly DaprClient _dapr;
    private readonly IClientManager _clientManager;

    public ImageStreamAdapter(DaprClient dapr, IClientManager clientManager)
    {
        _dapr = dapr;
        _clientManager = clientManager;
    }

    public async Task StartAsync()
    {
        await _dapr.SubscribeToTopicAsync("image-events", async (ImageEntity image) =>
        {
            var url = $"https://cdn.alex.rocks/{image.EventId}/{image.Id}.jpg";
            await _clientManager.PushAsync(url);
        });
    }
}

This code uses Dapr’s client library to subscribe to a topic named image-events. Whenever a new image is published from the outbox, the adaptor constructs a CDN URL and pushes it to connected browsers or mobile devices. By offloading the details of subscription, retry, and error handling to Dapr, the adaptor remains clear and focused on delivering the user experience.

Of course, not every situation is a clean success case. During early prototypes, I discovered that some cameras would go offline unexpectedly, causing outbox messages to pile up. Dapr’s built-in observability features helped me track down these issues quickly. By enabling metrics and logs at the sidecar level, I could see when a capture service lost connectivity or when the outbox processing fell behind. Integrating with Prometheus and Grafana gave me a real-time dashboard showing the health of the entire system. These insights led to optimizations in the retry policies and improved network equipment at certain venues. The result is a robust system where we spot problems early and fix them before they ruin the live experience. Lightstreamer also provides connection statistics, letting me know if clients are dropping or if the data rate is too high. Together, these tools create a feedback loop that keeps WedCam reliable.

One of the most exciting aspects of this architecture is how easily it accommodates new features. For example, we recently rolled out a guest chat service. Guests watching the stream can post messages that appear alongside the images. This service uses the same Dapr pub/sub component as the image stream, but with a different topic. Lightstreamer handles delivery, so chat messages arrive in real time. Because the foundation is already in place, adding this feature required minimal code.

Another milestone was the introduction of a guest management and RSVP system. This module lets couples manage invite lists and enables guests to confirm attendance directly from the WedCam portal. Dapr’s state store keeps track of each RSVP, while a binding triggers our catering partner’s quoting service whenever guest numbers change. By automating these updates, we ensure accurate catering quotes without manual coordination. It has streamlined our planning workflow and given couples insight into how many meals they’ll need on the big day.

Another upcoming idea is automatic photo curation. Using serverless functions triggered by Dapr, we plan to analyze image quality and highlight the best shots for a quick recap at the end of the event. Each addition builds on the existing Dapr components and Lightstreamer channels, preserving the overall simplicity while steadily improving the user experience.

In the end, WedCam has become more than just a way to share wedding photos. It’s a living demonstration of how Dapr’s sidecars and Lightstreamer can combine to deliver complex, low-latency experiences with surprisingly little code. The outbox pattern ensures no images are lost even when something goes wrong. The CDN distributes heavy assets to the edges, while Lightstreamer keeps viewers updated in real time. Guests feel closer to the action, and couples have a record of their special day that spans continents. For developers, Dapr provides a set of building blocks that free us from boilerplate and make new features almost plug-and-play. As we continue to innovate on WedCam, I’m excited to see how this combination of open-source tooling and cloud technology will transform the way we share memories across the globe.

Conclusion

WedCam’s journey has shown me the power of combining Dapr and Lightstreamer for low-latency data streaming. By abstracting complex patterns like the outbox through Dapr, I’m free to focus on building engaging features. Lightstreamer delivers those features in real time to guests, while CDNs ensure global accessibility. Whether dealing with unreliable networks or scaling for thousands of viewers, the architecture adapts gracefully. If you’re building anything that requires fast, reliable event delivery—be it weddings, sporting events, or live auctions—I encourage you to explore this combination. Dapr sidecars take care of the plumbing, Lightstreamer handles the distribution, and your application shines at the center.