Skip to Content

The post would demonstrate the creation of Micro-service for Comment-as-a-micro service scenario, using the Spring Cloud – making use of

Deploy the micro service on Cloud Foundry in SAP Cloud Platform.Use SAP Cloud Platform API Management to secure, publish,analyse and monetize APIs and micro services to internal and external consumers.

Click here to find the code used.

Check out how to monetize APIs using SAP Cloud Platform API Management

Motivation for using Spring Cloud

  • Distributed/versioned configuration
  • Service registration and discovery
  • Routing
  • Service-to-service calls
  • Load balancing
  • Circuit Breakers
  • Global locks
  • Leadership election and cluster state
  • Distributed messaging

 

Components involved

 

 

 

Zuul Gateway

Zuul is the front door for all requests from devices and web sites to the back-end of the service. Zuul is built to enable dynamic routing, monitoring, resiliency and security.It uses a range of different types of filters. These filters help perform the following functions:

  • Authentication and Security – identifying authentication requirements for each resource and rejecting requests that do not satisfy them.
  • Insights and Monitoring – tracking meaningful data and statistics in order to give an accurate view of production.
  • Dynamic Routing – dynamically routing requests to different back-end clusters as needed.
  • Stress Testing – gradually increasing the traffic to a cluster in order to gauge performance.
  • Load Shedding – allocating capacity for each type of request and dropping requests that go over the limit.
  • Static Response handling – building some responses directly instead of forwarding them to an internal cluster
  • Multi region Resiliency – routing requests across  regions in order to diversify  ELB usage and move  edges closer to users

Eureka Server

Eureka is a REST (Representational State Transfer) based service that is primarily used in locating services for the purpose of load balancing and fail-over of middle-tier servers.

Hystrix Circuit Breaker

Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and third party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.It helps in

  • Latency and Fault Tolerance -Stop cascading failures. Fallback and graceful degradation. Fail fast and rapid recovery.
  • Real Time Operation-Real time monitoring and configuration changes. Watch service and property changes take effect immediately as they spread across a fleet.
  • Concurrency -Parallel execution. Concurrency aware request caching. Automated batching through request collapsing.

 

CommentController.java –Check out the code here

@RestController
public class CommentController {

	private CommentService commentService;
	private ResourceService resourceService;
	public static final String PLAIN_RESPONSE_TYPE = "text/plain";
	public static final String JSON_RESPONSE_TYPE = "application/json";
	public static final String COLLECTION_NAME_COMMENTS = "commentsCollection";
	public static final String COLLECTION_NAME_RESOURCE = "resourceCollection";

	boolean USE_CF = false;
	String MONGO_LOCAL_HOST_URL = "mongodb://localhost:27017";

	@Autowired
	public void setCommentService() {
		this.commentService = new CommentServiceImpl(COLLECTION_NAME_COMMENTS, USE_CF, MONGO_LOCAL_HOST_URL);
		this.resourceService = new ResourceServiceImpl(COLLECTION_NAME_RESOURCE, USE_CF, MONGO_LOCAL_HOST_URL);
	}

	@ApiOperation(value = "Ping Comment Service")
	@RequestMapping(value = "/comment/ping", method = RequestMethod.GET, produces = PLAIN_RESPONSE_TYPE)
	public ResponseEntity<?> pingCommentService() {
		return new ResponseEntity<>("Ping Comment Successful", HttpStatus.OK);
	}

	// Can be used for bulk create
	@ApiOperation(value = "Create comments")
	@RequestMapping(value = "/comment/create", method = RequestMethod.POST, produces = JSON_RESPONSE_TYPE)
	public ResponseEntity<?> addComments(@RequestBody CreateAndReadResourceTO resourceTO) {

		ArrayList<CreateAndReadCommentsTO> allIncomingComments = resourceTO.getListOfComments();
		ResourceModel resourceModelObj = new ResourceModel();
		resourceModelObj.setResourceId(resourceTO.getResourceId());
		ArrayList<String> totalRootNodes = new ArrayList<String>();
		return new ResponseEntity<>(resourceTO, HttpStatus.OK);
	}

	@ApiOperation(value = "Update Comments")
	@RequestMapping(value = "/comment/update", method = RequestMethod.PUT, produces = JSON_RESPONSE_TYPE)
	public ResponseEntity<?> updateComments(@RequestBody UpdateResourceTO updatedResourceToObj) {

		ArrayList<UpdateCommentsTO> listOfUpdatedComments = updatedResourceToObj.getListOfCommentsToBeUpdated();
		ArrayList<UpdateCommentsTO> listOfUpdatedCommentsFromDb = new ArrayList<UpdateCommentsTO>();

		for (int i = 0; i < listOfUpdatedComments.size(); i++) {
			CommentModel updatedValues = commentService
					.updateComment(parseToCommentModel(listOfUpdatedComments.get(i)));
			if (updatedValues != null) {
				listOfUpdatedCommentsFromDb.add(parseCommentModelToUpdate(updatedValues));
			}
		}
		updatedResourceToObj.setListOfCommentsToBeUpdated(listOfUpdatedCommentsFromDb);
		return new ResponseEntity<>(updatedResourceToObj, HttpStatus.OK);
	}

	@ApiOperation(value = "List all comments available for Resource", response = Iterable.class)
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Successfull in finding the comments from the "),
			@ApiResponse(code = 404, message = "The resource you were trying to reach is not found") })
	@RequestMapping(value = "/comment/listAll/resource/{resourceId}", method = RequestMethod.GET, produces = "application/json")
	public CreateAndReadResourceTO listComments(@PathVariable String resourceId) {
		// get thelist of all the comment-ids associated with the resource
		ArrayList<String> listOfTopIds = this.resourceService.getAllCommentsOfTheResource(resourceId);
		Map<String, CommentModel> commentModelObj = commentService.findAllComments(listOfTopIds);
		CreateAndReadResourceTO resourceToObj = new CreateAndReadResourceTO();
		resourceToObj.setResourceId(resourceId);
		ArrayList<CreateAndReadCommentsTO> totalCommentsList = new ArrayList<CreateAndReadCommentsTO>();

		for (String key : commentModelObj.keySet()) {
			if (listOfTopIds.contains(key)) {
				CommentModel eachComment = commentModelObj.get(key);
				String childCommentId = eachComment.getChildCommentId();
				ArrayList<CommentModel> listOfCommentsStack = new ArrayList<CommentModel>();
				listOfCommentsStack.add(eachComment);
				CommentModel childComment;
				while (childCommentId != null) {
					boolean doesContainChildInMap = commentModelObj.containsKey(childCommentId);
					if (doesContainChildInMap == true) {
						childComment = commentModelObj.get(childCommentId);
						listOfCommentsStack.add(childComment);
						childCommentId = childComment.getChildCommentId();
					}
				}
				CreateAndReadCommentsTO previouscommentsTO = null;

				for (int i = listOfCommentsStack.size() - 1; i >= 0; i--) {
					CreateAndReadCommentsTO currentcommentsTO = parseCommentModel(listOfCommentsStack.get(i));
					if (previouscommentsTO != null) {
						currentcommentsTO.setChildComment(previouscommentsTO);
					}
					previouscommentsTO = currentcommentsTO;
				}

				totalCommentsList.add(previouscommentsTO);
			}
		}
		resourceToObj.setListOfComments(totalCommentsList);

		return resourceToObj;
	}

	@ApiOperation(value = "Delete a comment")
	@RequestMapping(value = "/comment/resource/{resourceId}/delete/{commentId}", method = RequestMethod.DELETE, produces = PLAIN_RESPONSE_TYPE)
	public ResponseEntity<?> deleteComment(@PathVariable String resourceId, @PathVariable String commentId) {
		ArrayList<String> listOfTopIds = this.resourceService.getAllCommentsOfTheResource(resourceId);
		commentService.deleteComment(commentId);
		if (listOfTopIds.contains(commentId)) {
			// remove the comment from the resource as well
			resourceService.deleteRootComment(resourceId, commentId);
		}
		return new ResponseEntity<>("Comment deleted recursively-successfully", HttpStatus.OK);

	}

CommentCrudeOperations.java- Check out the whole code here

public class CommentCrudOperations {
	String collectionName;
	public final String DB_NAME = "commentsDB";
	public final String SEARCH_PARAM = "commentId";
	public final String UPDATE_PARAM = "commentString";

	boolean useCF;
	private MongoCollection<CommentModel> collection;

	public CommentCrudOperations(String colName, boolean usecf, String mongoUrl) {
		collectionName = colName;
		useCF = usecf;
		if (useCF == false) {
			MongoOperations.getInstance(false).bindToDb(mongoUrl, DB_NAME);
		}
		MongoOperations.getInstance(useCF).createCollection(collectionName);
		this.collection = MongoOperations.getInstance(useCF).getDb().getCollection(collectionName, CommentModel.class);
	}

	public Map<String, CommentModel> findAll(ArrayList<String> topLevelCommentIds) {

		Map<String, CommentModel> listOfCommentsFound = new HashMap<String, CommentModel>();

		for (int i = 0; i < topLevelCommentIds.size(); i++) {
			String eachId = topLevelCommentIds.get(i);
			CommentModel foundComment = collection.find(eq(SEARCH_PARAM, eachId)).first();
			if (foundComment != null) {
				String searchId = foundComment.getChildCommentId();
				listOfCommentsFound.put(foundComment.getCommentId(), foundComment);
				
			}

		}

		return listOfCommentsFound;
	}

	public CommentModel findOne(String commentId) {
		CommentModel foundUser = collection.find(eq(SEARCH_PARAM, commentId)).first();
		return foundUser;
	}

	public CommentModel save(CommentModel commentObj) {

		collection.insertOne(commentObj);
		return commentObj;

	}

	public ArrayList<CommentModel> saveMany(ArrayList<CommentModel> commentCollection) {
		collection.insertMany(commentCollection);
		return commentCollection;

	}

	public ArrayList<CommentModel> delete(String commentId) {
		ArrayList<CommentModel> listOfDeletedComments = new ArrayList<CommentModel>();

		while (commentId != null) {
			CommentModel deletedComment = collection.findOneAndDelete(eq(SEARCH_PARAM, commentId));
			listOfDeletedComments.add(deletedComment);
			if (deletedComment != null) {
				commentId = deletedComment.getChildCommentId();
			} else {
				commentId = null;
			}

		}
		return listOfDeletedComments;
	}

	public ArrayList<CommentModel> updateComments(ArrayList<CommentModel> commentObjList) {
		ArrayList<CommentModel> updatedComments = new ArrayList<CommentModel>();
		for (int i = 0; i < commentObjList.size(); i++) {
			CommentModel eachComment = commentObjList.get(i);
			collection.findOneAndReplace(eq(SEARCH_PARAM, eachComment.getUserId()), eachComment);
			updatedComments.add(eachComment);
		}
		return commentObjList;
	}

	public CommentModel updateComment(CommentModel eachComment) {
		Bson filter = Filters.eq(SEARCH_PARAM, eachComment.getCommentId());
		Bson updates = Updates.set(UPDATE_PARAM, eachComment.getCommentString());

		CommentModel updatedValue = collection.findOneAndUpdate(filter, updates);
		return updatedValue;
	}

}

 

Once the jars are ready the application can be deployed on Cloud Foundry in SAP Cloud Platform

cf api https://api.cf.sap.hana.ondemand.com
cf login
Once the app is deployed then 
//create the mongo service
cf create-service mongodb v3.0-dev mongoserv
//bind the service to the app
cf bind-service commentAsService mongoserv
//restage the app
cf restage commentAsService 

Once the app is running the Swagger Spec would show all the available APIs hosted by the micro service.

Now that the service is available, let the stakeholders and customers use the service ,but first make sure the service is protected ,metered and throttled.

Check out this post how to protect,meter and throttle the APIs  

To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply