48

API Execution Context

 5 years ago
source link: https://www.tuicool.com/articles/hit/vEzqyey
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

What Is Required?

A POJO that composes parameters like user ID, user region, user locale, user location, API invocation ID, etc. that cuts across code layers while an API is being exercised.

Why Is It Required?

  • Any API business logic (BL) operation operates on resources pertaining to a user. User context is inevitable to enforce access control.
  • BL uses API invocation ID to augment logs so that logs pertaining to a API execution are traceable.
  • Many other such parameters might be required for all layers in the code depending on the service.

If one POJO can compose all such parameters, then the POJO can be passed as an argument to all methods instead passing each cross-cutting parameter as an argument. So, the signature of methods would be clean and lean and the POJO can be augmented with more cross-cutting parameters without changing the signature of methods.

How Is It Implemented?

Execution context is:

  • POJO composing cross-cutting parameters.

  • Immutable so that any layer in the service can not manipulate cross cutting parameters.

  • Only service layers like servlet filters can instantiate it with appropriate arguments.

Let's discuss different approaches and decide on one.

Telescoping Constructor Pattern

public final class Context {
 private String apiInvocationId;
 private String userId;
 private String authTicket;
 private String xyz;
 private String abc;

 // initially service needs only tid, realmId
 public Context(String apiInvocationId, String userId) {
  this.apiInvocationId = apiInvocationId;
  this.userId = userId;
 }

 // 3 months down the line, service needs auth ticket to make down stream
 // calls on API caller behalf
 public Context(String apiInvocationId, String userId, String authTicket) {
  this(apiInvocationId, userId);
  this.authTicket = authTicket;
 }

 // down the line need for some more parameters
 public Context(String apiInvocationId, String userId, String authTicket, String xyz, String abc) {
  this(apiInvocationId, userId, authTicket);
  this.xyz = xyz;
  this.abc = abc;
 }

 public String getApiInvocationId() {
  return apiInvocationId;
 }

 public String getUserId() {
  return userId;
 }

 public String getAuthTicket() {
  return authTicket;
 }

 public String getXyz() {
  return xyz;
 }

 public String getAbc() {
  return abc;
 }
}

Pros

  • Context is immutable

Cons

new Context("apiInvocationId", "userId", null, "xyz", null)

Bottom Line

The pattern works but is hard to maintain.

JavaBeans Pattern

public final class Context {
 private String apiInvocationId;
 private String userId;
 private String authTicket;
 private String xyz;
 private String abc;
 public String getApiInvocationId() {
  return apiInvocationId;
 }
 public void setApiInvocationId(String apiInvocationId) {
  this.apiInvocationId = apiInvocationId;
 }
 public String getUserId() {
  return userId;
 }
 public void setUserId(String userId) {
  this.userId = userId;
 }
 public String getAuthTicket() {
  return authTicket;
 }
 public void setAuthTicket(String authTicket) {
  this.authTicket = authTicket;
 }
 public String getXyz() {
  return xyz;
 }
 public void setXyz(String xyz) {
  this.xyz = xyz;
 }
 public String getAbc() {
  return abc;
 }
 public void setAbc(String abc) {
  this.abc = abc;
 }



}

Pros

  • Solves cons of telescoping constructor pattern because it can be augmented with more setter/getter methods as and when required.

Cons

  • Context is not immutable.

Bottom Line

Violates all constraints in our implementation discussion due to cons.

Builder Pattern

public final class Context {

 private final String userId;
 private final String apiInvocationId;

 public static class Builder {
  private String userId = null;
  private String apiInvocationId = null;

  public Builder() {

  }

  public Builder realmId(String userId) {
   this.userId = userId;
   return this;
  }

  public Builder tid(String apiInvocationId) {
   this.apiInvocationId = apiInvocationId;
   return this;
  }

  public Context build() {
   return new Context(this);
  }

  // Add methods here, if there is need to capture more crosscutting parameters
 }

 private Context(Builder builder) {
  this.userId = builder.userId;
  this.apiInvocationId = builder.apiInvocationId;
  // put more parameters when there is need to to capture more
 }

 public String getUserId() {
  return userId;
 }

 public String getApiInvocationId() {
  return apiInvocationId;
 }

}

Pros

  • Solves cons of telescoping constructor pattern, as it can be augmented with more builder setter methods as and when required.
  • Solves cons of beans pattern also.
  • Context is immutable.
  • Instantiation code much cleaner to read/write with builder methods.

Conclusion

The builder pattern is the optimal pattern to implement execution context for any API in any service. Now, lets discuss how to implemet it using with very less code using the Immutables library .

import org.immutables.value.Value;

@Value.Immutable
@Value.Style(strictBuilder = true)
public interface Context {

    String getUserId();

    String getAPIInvocationId();

    String getAuthTicket();

    String getRegion();

  // keep adding more attributes as and when needed that is all
}

The Immutable library generates an immutable implementation context with appropriate builders and getters. All we need to do is add an attribute (single line) to the interface context as and when needed — that's all.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK