Browser docs

Ambassador

Intent

Provide a helper service instance on a client and offload common functionality away from a shared resource.

Explanation

Real world example

A remote service has many clients accessing a function it provides. The service is a legacy application and is impossible to update. Large numbers of requests from users are causing connectivity issues. New rules for request frequency should be implemented along with latency checks and client-side logging.

In plain words

With the Ambassador pattern, we can implement less-frequent polling from clients along with latency checks and logging.

Microsoft documentation states

An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features.

Programmatic Example

With the above introduction in mind we will imitate the functionality in this example. We have an interface implemented by the remote service as well as the ambassador service:

1interface RemoteServiceInterface {
2    long doRemoteFunction(int value) throws Exception;
3}

A remote services represented as a singleton.

 1@Slf4j
 2public class RemoteService implements RemoteServiceInterface {
 3    private static RemoteService service = null;
 4
 5    static synchronized RemoteService getRemoteService() {
 6        if (service == null) {
 7            service = new RemoteService();
 8        }
 9        return service;
10    }
11
12    private RemoteService() {}
13
14    @Override
15    public long doRemoteFunction(int value) {
16        long waitTime = (long) Math.floor(Math.random() * 1000);
17
18        try {
19            sleep(waitTime);
20        } catch (InterruptedException e) {
21            LOGGER.error("Thread sleep interrupted", e);
22        }
23
24        return waitTime >= 200 ? value * 10 : -1;
25    }
26}

A service ambassador adding additional features such as logging, latency checks

 1@Slf4j
 2public class ServiceAmbassador implements RemoteServiceInterface {
 3  private static final int RETRIES = 3;
 4  private static final int DELAY_MS = 3000;
 5
 6  ServiceAmbassador() {
 7  }
 8
 9  @Override
10  public long doRemoteFunction(int value) {
11    return safeCall(value);
12  }
13
14  private long checkLatency(int value) {
15    var startTime = System.currentTimeMillis();
16    var result = RemoteService.getRemoteService().doRemoteFunction(value);
17    var timeTaken = System.currentTimeMillis() - startTime;
18
19    LOGGER.info("Time taken (ms): " + timeTaken);
20    return result;
21  }
22
23  private long safeCall(int value) {
24    var retries = 0;
25    var result = (long) FAILURE;
26
27    for (int i = 0; i < RETRIES; i++) {
28      if (retries >= RETRIES) {
29        return FAILURE;
30      }
31
32      if ((result = checkLatency(value)) == FAILURE) {
33        LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
34        retries++;
35        try {
36          sleep(DELAY_MS);
37        } catch (InterruptedException e) {
38          LOGGER.error("Thread sleep state interrupted", e);
39        }
40      } else {
41        break;
42      }
43    }
44    return result;
45  }
46}

A client has a local service ambassador used to interact with the remote service:

 1@Slf4j
 2public class Client {
 3  private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
 4
 5  long useService(int value) {
 6    var result = serviceAmbassador.doRemoteFunction(value);
 7    LOGGER.info("Service result: " + result);
 8    return result;
 9  }
10}

Here are two clients using the service.

1public class App {
2  public static void main(String[] args) {
3    var host1 = new Client();
4    var host2 = new Client();
5    host1.useService(12);
6    host2.useService(73);
7  }
8}

Here’s the output for running the example:

1Time taken (ms): 111
2Service result: 120
3Time taken (ms): 931
4Failed to reach remote: (1)
5Time taken (ms): 665
6Failed to reach remote: (2)
7Time taken (ms): 538
8Failed to reach remote: (3)
9Service result: -1

Class diagram

alt text

Applicability

Ambassador is applicable when working with a legacy remote service which cannot be modified or would be extremely difficult to modify. Connectivity features can be implemented on the client avoiding the need for changes on the remote service.

  • Ambassador provides a local interface for a remote service.
  • Ambassador provides logging, circuit breaking, retries and security on the client.

Typical Use Case

  • Control access to another object
  • Implement logging
  • Implement circuit breaking
  • Offload remote service tasks
  • Facilitate network connection

Known uses

Credits