Fundamentals of testing microservices architecture

Increased adoption of digital has pushed the need for speed to the forefront. The need to conceptualize, develop, launch new products and iterate to make them better, much ahead of the competition and gain customer mindshare, has become the critical driver of growth. Adoption of agile principles or movement towards scrum teams for increased agility are all steps in this direction. Disruptive changes have also taken place on the application front with the 3-tier architecture of the late 90s and subsequent 2 tier monolithic architecture giving way to one that is based on microservices.

Having a single codebase made a monolithic architecture less risky but slow to adopt changes, the exact opposite of a services-based architecture. Microservices architecture makes it easier for multiple development teams to make changes to the codebase in parallel. By transforming an application into a distributed set of services that are highly independent, yet interdependent, provides the ability to create new services/functionalities and modify services without impacting the overall application. These changes can be achieved by teams cutting across geographies or locations and makes it easier for them to understand functional modules rather than the humongous application codebase. However, the highly distributed nature of services also gives them a heightened ability to fail.

Breaking it down

At the core, a microservices architecture comprises of 3 layers – a REST layer that allows the service to expose APIs, the database layer, and the service layer. A robust testing strategy needs to cover all these layers and ensure that issues are not leaked to production. The further an issue moves across stages, the impact increases on account of multiple teams getting affected. Hence the test plan must cover multiple types of testing like service testing, subsystem testing, client acceptance testing, performance testing, etc. Subsequent paragraphs outline key aspects of service level testing and integration testing in a microservices architecture-based application landscape.

In service level testing, each service forming a part of the application architecture needs to be validated. Each service has dependencies on other services and transmits information to others based on need. In a monolith architecture, since connections are being established from one class to the other within the same Java Virtual machine (JVM), chances of failure are far lower. However, in a services architecture, these are distributed, driving the need for network calls to access other services and makes it more complex.

Functional Validation: The primary goal in services testing is the functionality validation of a service. Key to this is the need to understand all events the service handles through both internal as well as external APIs. At times this calls for simulating certain events to ensure that they are being handled properly by the service. Collaboration with the development team is key to understand incoming events being handled by the service as part of its functionality. A key element of functional validation – API contract testing, tests the request and response payload along with a host of areas like pagination and sorting behaviors, metadata, etc.

Compatibility: Another important aspect is recognizing and negating backward compatibility issues. This happens during the launch of a changed version of the service that breaks existing clients running in production. Changes that happen to API contracts need to be evaluated in detail to understand if they are mandatory and capable of breaking clients in production. An addition of a new attribute or a parameter may not classify as a breaking change; however, changes to response payload, behavior, error codes, or datatypes have the ability to break. A change in value typically changes the logic behind it as well. They need to be uncovered much earlier in the service testing lifecycle.

Dependencies: Another aspect of focus is external dependencies, where one would test both incoming as well as outgoing API calls. Since these are heavily dependent on the availability of other services and hence other teams, there is a strong need to obviate dependency through the usage of mocks. Having conversations with developers and getting them to insert mocks while creating individual services will enable testing dependencies without waiting for the service to be available. It is imperative to make sure the mocks are easily configurable without needing access to the codebase. Usage of mocks also drives ease in automation giving teams the ability to run independently with no configuration.

Bringing it all together

Once each service is tested for its functionality, the next step is to move onto validate how the various collaborating services work together end to end. Known as subsystem testing or integration testing, it tests the whole functionality exposed together. Understanding the architecture or application blueprint by discussions with the development team is paramount in this stage. Further, there is a strong need to use real services deployed in the integration environment rather than mocks used for external dependencies.

As part of integration testing, there is a need to validate if the services are wired very closely and talking to each other. The event stream and inter-service API calls need to be configured properly so inter-service communication channels are proper. If the service functionality level testing is proper, the chances of finding errors are minimal in this stage, since the required mocks created in the functionality testing stage would have ensured that the services function properly.

Looking in-depth, we find that the testing strategies for microservices are not extremely different from those adopted for a monolith application architecture. The fundamental difference comes in the way the interdependencies and communication between multiple services forming a part of the larger application are tested to ensure that the application as a whole function in line with expectations.

SharePoint Online and Salesforce Integration

SharePoint Online and Salesforce are two different cloud platforms, provided as “Software as a Service” from two different vendors.

SharePoint is a cloud-based “Software as a Service” using which organizations can share content with colleagues, partners, and customers. SharePoint online also provides flexibility to access it from anywhere, i.e. home or office or wherever Internet connectivity is available, and from any device, i.e. mobile/compact.

At the other end of the spectrum,  Salesforce is a cloud-based customer relationship management software solution for sales, service, marketing, collaboration, analytics, and building reports.

Both the above can be considered as “Software as a Service” platforms.

In most business situations, there may be a use case, where business users would like to seamlessly obtain data from Salesforce on to SharePoint online, so that, the information can be collaborated from one single platform, instead of two different places.

In this blog, I will explain the concept and steps required to integrate Salesforce with the SharePoint Online site, so that, information updated or created in Salesforce will, in real-time, be updated and created in SharePoint online site.

When the question arises as to how to write data in SharePoint online, the first hurdle is to remotely authenticate the user against it, and in order to remotely authenticate the user, the best way available is REST api.

You need to make few rest calls to get the authentication piece as given below:

  • Get the security token
  • Get the access token
  • Get the request digest

The above rest call should be translated into an apex class which is supported by force.com

The first task is to get the SharePoint online security token, which can be obtained by posting XML as the request body to https://login.microsoftonline.com/extSTS.srf. In the XML you need to pass on account credentials which has at least contribute access.

The above request would fetch a response security token which is needed to get the access token.

In order to get the access token, again a rest call is required to be posted to the following URL with the security token as the request body:

https://yourdomain.sharepointonline.com/_forms/default.aspx?wa=wsignin1.0

The rest call with the request body including security token to the above URL would fetch a response, which would contain cookies and these cookies must be included in all the subsequent rest calls.

After the above operation, you need to have the request digest. The request digest is obtained by posting the rest call along with the access token and the obtained cookies to the following URL:

https://yourdoamin.sharepointonline.com/_api/contextinfo.

The rest call that is posted to the above URL will fetch the response along with the request digest. Please note that the entire contents of the “FormDigestValue” tag would be required which includes the date-time portion as well as time zone offset.

All the above steps have to be carried out in terms of a global apex class, and call the apex class with the trigger in salesforce.

Apex class source code is as given below: 

global class SharePointOnlineWebserviceCallout{
 @future (callout=true)
 Public static Void GetAuthentication(string AccountTitle)
 {
 string body = '';
 string formattedCookie = '';
 string output = '';
 string cookie = '';
 string token = '';
 string username = 'account@sharepoint.com';
 string password = 'Password';
 string host = 'https://yourdoamin.sharepointonline.com';
 string tokenRequestXml ='<s:Envelope ' +
 'xmlns:s='http://www.w3.org/2003/05/soap-envelope' ' +
 'xmlns:a='http://www.w3.org/2005/08/addressing' ' +
 'xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'> ' +
 '<s:Header>' +
 '<a:Action s_mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>' +
 '<a:ReplyTo> ' +
 '<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address> ' +
 '</a:ReplyTo>' +
 '<a:To s_mustUnderstand='1'>https://login.microsoftonline.com/extSTS.srf</a:To> ' +
 '<o:Security ' +
 's:mustUnderstand='1' ' +
 'xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'> ' +
 '<o:UsernameToken> ' +
 '<o:Username>' + username + '</o:Username>' +
 '<o:Password>' + password + '</o:Password>' +
 '</o:UsernameToken>' +
 '</o:Security>' +
 '</s:Header>' +
 '<s:Body>' +
 '<t:RequestSecurityToken xmlns_t='http://schemas.xmlsoap.org/ws/2005/02/trust'> ' +
 '<wsp:AppliesTo xmlns_wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'> ' +
 '<a:EndpointReference> ' +
 ' <a:Address>' + host + '</a:Address> ' +
 '</a:EndpointReference> ' +
 '</wsp:AppliesTo> ' +
 ' <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType> ' +
 '<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType> ' +
 ' <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType> ' +
 '</t:RequestSecurityToken> ' +
 ' </s:Body> ' +
 '</s:Envelope>';

HttpRequest reqBinaryToken = new HttpRequest();
reqBinaryToken.setEndpoint(‘https://login.microsoftonline.com/extSTS.srf’);
reqBinaryToken.setMethod(‘POST’);
reqBinaryToken.setbody(tokenRequestXml);
reqBinaryToken.setHeader(‘Content-Length’,String.valueof(tokenRequestXml.length()));
reqBinaryToken.setTimeout(60000);

HttpResponse responseBinaryToken = new HttpResponse();
Http httpBinaryToken = new Http();
responseBinaryToken = httpBinaryToken.send(reqBinaryToken);
string xmlContent = responseBinaryToken.getBody();
Dom.Document doc = responseBinaryToken.getBodyDocument();
Dom.XMLNode address = doc.getRootElement();
//XmlStreamReader reader = new XmlStreamReader(responseBinaryToken.getBody());
string outxmlstring = String.valueof(doc.getRootElement().getName());//gives you root element Name

XmlStreamReader reader = new XmlStreamReader(responseBinaryToken.getBody());
while(reader.hasNext()) {
if (reader.getEventType() == XmlTag.START_ELEMENT && reader.getLocalName()== ‘BinarySecurityToken’) {
reader.next();
if(reader.hasNext()){
if(reader.getEventType() == XmlTag.CHARACTERS){
token = reader.getText();
token += ‘&p=’;
}
}
}
reader.next();
}

HttpRequest requestCookie = new HttpRequest();
requestCookie.setEndpoint(‘https://yourdoamin.sharepointonline.com/_forms/default.aspx?wa=wsignin1.0’);
requestCookie.setHeader(‘Content-Type’, ‘application/x-www-form-urlencoded’);
requestCookie.setHeader(‘User-Agent’,’Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)’);
requestCookie.setMethod(‘POST’);
requestCookie.setBody(token);
requestCookie.setHeader(‘Content-Length’,String.valueof(token.length()));

HttpResponse responseCookie = new HttpResponse();
Http httpCookie = new Http();
responseCookie = httpCookie.send(requestCookie);
string location = responseCookie.getHeader(‘Location’);

if(responseCookie.getStatus() == ‘MovedPermanently’){
HttpRequest reqMovedPermanently = new HttpRequest();
reqMovedPermanently.setHeader(‘Content-Type’, ‘application/x-www-form-urlencoded’);
reqMovedPermanently.setMethod(‘POST’);
reqMovedPermanently.setEndpoint(‘https://yourdoamin.sharepointonline.com/_forms/default.aspx?wa=wsignin1.0’);
reqMovedPermanently.setBody(token);
reqMovedPermanently.setHeader(‘Content-Length’,String.valueof(token.length()));
reqMovedPermanently.setHeader(‘Location’, location);
reqMovedPermanently.setHeader(‘User-Agent’,’Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)’);
HttpResponse responseMovedPermanently = new HttpResponse();
Http httpMovedPermanently = new Http();
responseMovedPermanently = httpMovedPermanently.send(reqMovedPermanently);
cookie = responseMovedPermanently.getHeader(‘Set-Cookie’);
}
else
{
cookie = responseCookie.getHeader(‘Set-Cookie’);
}

HttpRequest requestDigest = new HttpRequest();
requestDigest.setEndpoint(‘https://yourdoamin.sharepointonline.com/_api/contextinfo’);
requestDigest.setMethod(‘POST’);
requestDigest.setBody(body);
requestDigest.setHeader(‘Content-Length’,String.valueof(body.length()));
requestDigest.setHeader(‘Accept’,’application/json;odata=verbose’);
requestDigest.setHeader(‘Content-Type’,’application/json;odata=verbose’);
requestDigest.setHeader(‘Cookie’,cookie);

Http httpRequestDigest = new Http();
HttpResponse responseRequestDigest = new HttpResponse();
responseRequestDigest = httpRequestDigest.send(requestDigest);
string requestDigestValue = responseRequestDigest.toString();
string xmlContentRequestDigest = responseRequestDigest.getBody();

Integer index1 = xmlContentRequestDigest.indexOf(‘”FormDigestValue”:”‘);

Integer index2 = ‘”FormDigestValue”:”‘.length();

string contentRequestDigest = xmlContentRequestDigest.Substring(index1 + index2);

string requestDigestXml = contentRequestDigest.split(‘”‘)[0];

HttpRequest reqWrite = new HttpRequest();
HttpResponse resWrite = new HttpResponse();
Http httpWrite = new Http();
reqWrite.setEndpoint(‘https://yourdoamin.sharepointonline.com/_api/web/lists/GetByTitle(‘AccountTest’)/items’);
reqWrite.setMethod(‘POST’);
reqWrite.setCompressed(false);
reqWrite.setHeader(‘Accept’

Partner with us to ensure seamless collaboration and communication across the enterprise with SharePoint.