Hi, I’m Bago! Last time after writing “Taobao” open platform interface design ideas, many fans mentioned when the next article will be published, so today I plan to write some ideas of open platform SDK design, at the same time, I will do some practice, step by step to implement my design ideas.

Before writing this article, I have used some SDk of Taobao and JINGdong open platform for my work, and I have learned a lot of design ideas. This time, I plan to implement my ideas into a usable SDk. The design will be roughly divided into three modules, which are as follows:

  • Data transmission module: mainly used to transmit request data, this paper uses HTTP protocol to transmit data
  • Serialization module: the user serializes and deserializes the data, SDK is for the customer to use should as far as possible to meet the customer’s will, such as the customer’s first way of serialization for JSON that, as the user will certainly hope SDK can support JSON serialization and deserialization
  • Application module: mainly coordinate data transmission, serialization between the work, at the same time to do some data check signature operations

The data transfer

The data transmission protocol uses the more common HTTP protocol. At the beginning, I wanted to make a general design to support other transmission modes, such as supporting socket to keep long links. Later, I thought there was no need to over-design. In fact, HTTP support may remain unchanged after the future, there is no need for this!

There are many open source HTTP tools, such as HttpClient, OKHTTP and URLConnection, which one to choose? A good idea is to abstract out an interface yourself and provide a default implementation class so that you can maintain some flexibility.

/** * public interface HttpClient {/** * perform HTTP post request * @param URL * @param data * @param headers * @return */ String post(String url, byte[] data, Map<String, List<String>> headers) throws HttpClientException; }Copy the code

HttpClient only defines a post method, which can satisfy the requirements. Most open platform interfaces are post requests, there is no need to define all HTTP features, a post is dry!

HttpURLConnection: HttpURLConnection:

package com.javaobj.sdk.http.impl; public class DefaultHttpClient implements HttpClient { public String post(String url, byte[] data, Map<String, List<String>> headers) throws HttpClientException { URL targetUrl = null; try { targetUrl = new URL(url); } catch (MalformedURLException e) {throw new HttpClientException(" Accessed url link is incorrect "); } try { HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection(); connection.setDoInput(true); connection.setDoOutput(true); connection.setRequestMethod("POST"); // Set the headers if(headers! = null){ Map<String, List<String>> tmp = new HashMap<String, List<String>>(headers); for(Map.Entry<String, List<String>> entry : tmp.entrySet()){ for(String value: entry.getValue()){ connection.setRequestProperty(entry.getKey(), value); } } } connection.connect(); / / send the request data OutputStream out = connection. GetOutputStream (); out.write(data); out.close(); / / read request response InputStream in = connection. The getInputStream (); ByteOutputStream body = new ByteOutputStream(); int ch; while ((ch = in.read()) ! = -1){ body.write(ch); } in.close(); String responseBody = new String(body.getBytes()); int statusCode = connection.getResponseCode(); boolean is2xxSuccessful = statusCode >= 200 && statusCode <= 299; boolean is3xxSuccessful = statusCode >= 300 && statusCode <= 399; // Determine whether the request was successful. (is2xxSuccessful || is3xxSuccessful)) { throw new HttpStatusClientException(statusCode, connection.getResponseMessage(), responseBody); } return responseBody; } catch (IOException e) { throw new HttpClientException(e); }}}Copy the code

Above has realized a simple HTTP client, basically meets the demand.

Serialization and deserialization

Serialization and deserialization are to convert data between Java entities. In order to design a flexible point, here we define an interface for serialization and deserialization respectively:

  • Serializer
  • Deserializer

Also using Jackson library to do a simple implementation:

JacksonSerializer

public class JacksonSerializer implements Serializer { private final ObjectMapper objectMapper = new ObjectMapper(); public JacksonSerializer() { objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } public byte[] serialize(Object obj) { try { return objectMapper.writeValueAsBytes(obj); } catch (JsonProcessingException e) { throw new IllegalStateException(e); }}}Copy the code

JacksonDeserializer

public class JacksonDeserializer implements Deserializer { private final ObjectMapper objectMapper = new ObjectMapper();  public JacksonDeserializer() { objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false); } public <T> T deserialize(byte[] data, Class<T> clz) { try { return objectMapper.readValue(data, clz); } catch (IOException e) { throw new IllegalStateException(e); }}}Copy the code

This is a simple serialization and deserialization tool, which basically meets the requirements, but most open platforms have format parameters, which can be JSON, XML, etc. To integrate serialization and deserialization, I also created a SerializationAdapter interface. Inherit Serializer and Deserializer:

public interface SerializationAdapter extends Serializer, Deserializer{/** * Check whether data format is supported * @return */ Boolean support(String format); }Copy the code

Add SerializationAdapter. Support method is used to judge whether it supports the specified data format, such as adaptation json data format, create an implementation class JSONSerializationAdapter:

JSONSerializationAdapter

public class JSONSerializationAdapter implements SerializationAdapter {
    
    private final Serializer serializer;
    private final Deserializer deserializer;
    
    public JSONSerializationAdapter(Serializer serializer, Deserializer deserializer) {
        this.serializer = serializer;
        this.deserializer = deserializer;
    }
    
   public JSONSerializationAdapter() {
        this(new JacksonSerializer(), new JacksonDeserializer());
    }

    
    @Override
    public boolean support(String format) {
        return format != null && Objects.equals(format.toLowerCase(), "json");
    }

    @Override
    public <T> T deserialize(byte[] data, Class<T> clz) {
        return deserializer.deserialize(data, clz);
    }

    @Override
    public byte[] serialize(Object obj) {
        return serializer.serialize(obj);
    }
}
Copy the code

This is more flexible, if the boss in the back secretly came to a sentence, this if can support XML is perfect, this requirement is very simple! If you’re using JAXB to serialize XML, write the serializer and deserializer first:

JAXBSerializer

public class JAXBSerializer implements Serializer { @Override public byte[] serialize(Object obj) { ByteOutputStream outputStream = new ByteOutputStream(); JAXB.marshal(obj, outputStream); return outputStream.getBytes(); }}Copy the code

JAXBDeserializer

public class JAXBDeserializer implements Deserializer { @Override public <T> T deserialize(byte[] data, Class<T> CLZ) {return jaxb. unmarshal(new StringReader(new String(data).trim())); }}Copy the code

XMLSerializationAdapter

public class XMLSerializationAdapter implements SerializationAdapter {

    private final Serializer serializer;
    private final Deserializer deserializer;

    public XMLSerializationAdapter(Serializer serializer, Deserializer deserializer) {
        this.serializer = serializer;
        this.deserializer = deserializer;
    }

    public XMLSerializationAdapter() {
        this(new JAXBSerializer(), new JAXBDeserializer());
    }

    @Override
    public boolean support(String format) {
        return format != null && Objects.equals(format.toLowerCase(), "xml");
    }

    @Override
    public <T> T deserialize(byte[] data, Class<T> clz) {
        return deserializer.deserialize(data, clz);
    }

    @Override
    public byte[] serialize(Object obj) {
        return serializer.serialize(obj);
    }
}
Copy the code

In the same way, other data formats can be customized, so I won’t go into details.

How to use it?

Use is also very simple, simple to write a test method test:

public class SerializationTest { @Test public void testSerialization(){ String format = "xml"; List<SerializationAdapter> adapters = new ArrayList<>(); adapters.add(new JSONSerializationAdapter()); adapters.add(new XMLSerializationAdapter()); Person person = new Person(); Person. Elegantly-named setName (" bully dog "); person.setAge(18); for(SerializationAdapter adapter : adapters){ if (adapter.support(format)) { byte[] data = adapter.serialize(person); System.out.println(format + "serialized data:" + new String(data)); Person person2 = adapter.deserialize(data, Person.class); System.out.println(format + "deserialized data:" + person2); }}}}Copy the code

In the above test code, you can convert between JSON and XML formats simply by changing the value of the format variable.

JSON output
Json deserialized data: {"name":" bully ","age":18} Json deserialized data: Person{name=' bully ', age=18}Copy the code
XML output
XML serialized data: <? The XML version = "1.0" encoding = "utf-8" standalone = "yes"? > <person> <age>18</age> <name> </name> </person>Copy the code

The API client

All that remains is to write a simple API client that initiates the HTTP request and processes the response data.

Public interface SdkClient {/** * execute API request * @param req * @param <T> * @return */ <T extends AbstractResponse> T execute(AbstractRequest<T> req); }Copy the code

Because the SDK is designed to be used by others, it is as simple as possible, and AbstractRequest and AbstractResponse are represented by entity classes.

AbstractRequest

Public abstract class AbstractRequest<T extends AbstractResponse> {/** ** request method name * @return */ public abstract String getMethod(); Public abstract Map<String, Object> getApiParams(); public abstract Map<String, Object> getApiParams(); /** * Request response entity * @return */ public abstract Class<T> getresponass (); }Copy the code

AbstractResponse

public class AbstractResponse { private String code; private String msg; public boolean isSuccess(){ return Objects.equals(code, "0"); }}Copy the code

DefaultClient

A simple API client

public class DefaultClient implements SdkClient { private SerializationAdapter serializationAdapter = new JSONSerializationAdapter(); private HttpClient httpClient = new DefaultHttpClient(); /** * request address */ private final String endpoint; /** * AppId */ private Final String AppId; /** * private final String AppSecret; public DefaultClient(String endpoint, String appid, String appsecret) { this.endpoint = endpoint; this.appid = appid; this.appsecret = appsecret; } @Override public <T extends AbstractResponse> T execute(AbstractRequest<T> req) { Map<String, Object> params = req.getApiParams(); String timestamp = System.currentTimeMillis() + ""; StringBuilder paramsStr = new StringBuilder(); Map<String, Object> treeMap = new TreeMap<>(params); for(Map.Entry<String, Object> entry : treeMap.entrySet()){ paramsStr.append(entry.getKey()).append(entry.getValue()); } // Calculate the signature // Application key + Application ID + Request method name + API version number + Request Parameter + Application key String sign = md5Util.md5 (AppSecret + AppID + req.getMethod() + req.getVersion() + paramsStr.toString() + appsecret ); String url = endpoint + "?" + "method=" + req.getMethod() + "&version=" + req.getVersion() + "&timestamp" + timestamp + "&sign" + sign + "&appid" + appid; Map<String, List<String>> headers = new HashMap<>(); headers.put("Content-type", Collections.singletonList("application/json")); byte[] body = serializationAdapter.serialize(req.getApiParams()); String response = httpClient.post(url, body, headers); return serializationAdapter.deserialize(response.getBytes(), req.getResponseClass()); }}Copy the code

conclusion

At this point, a simple open platform SDK is basically in place. In order to adapt to most customers’ open environments, the following points should be noted:

  • Try to develop with lower JDK versions, such as 1.5 and 1.6, covering most Java runtime/development environments
  • Reduce third-party dependencies and avoid version conflicts
  • Both the input and output parameters use Java entity classes to avoid incorrect parameter names and types when used by customers

There are also some attention points did not think of, we wide to the comment area message, learn to communicate together.