Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Layered Architecture for API Separation

Layered Architecture for API Separation

Avatar for Alan Chiu

Alan Chiu

April 25, 2016

Other Decks in Programming

Transcript

  1. LEG

  2. {
 "geocoded_waypoints" : [
 {
 "geocoder_status" : "OK",
 "place_id" :

    "Ei8zMjAxLTMyNDkgMTl0aCBTdCwgU2FuIEZyYW5jaXNjbywgQ0EgOTQxMTAsIFVTQQ",
 "types" : [ "street_address" ]
 },
 {
 "geocoder_status" : "OK",
 "place_id" : "EjA3OTEtNzk5IEJyYW5uYW4gU3QsIFNhbiBGcmFuY2lzY28sIENBIDk0MTAzLCBVU0E",
 "types" : [ "street_address" ]
 }
 ],
 "routes" : [
 {
 "bounds" : {
 "northeast" : {
 "lat" : 37.7731626,
 "lng" : -122.4031047
 },
 "southwest" : {
 "lat" : 37.7604717,
 "lng" : -122.4154241
 }
 },
 "copyrights" : "Map data ©2016 Google",
 "legs" : [
 {
 "distance" : {
 "text" : "1.4 mi",
 "value" : 2233
 },
 "duration" : {
 "text" : "7 mins",
 "value" : 443
 },
 "end_address" : "791-799 Brannan St, San Francisco, CA 94103, USA",
 "end_location" : {
 "lat" : 37.7731626,
 DIRECTIONS TO MARS BAR
  3. GOOGLE RESPONSE DTOS public class GoogleDirectionsResponseDTO {
 
 List<GoogleRouteDTO> routes;


    } public class GoogleRouteDTO {
 
 List<GoogleLegDTO> legs;
 } public class GoogleLegDTO {
 
 List<GoogleStepDTO> steps;
 } public class GoogleStepDTO {
 
 GoogleOverviewPolyLineDTO overviewPolyline;
 } public class GoogleOverviewPolyLineDTO {
 
 String points;
 }
  4. IMPLEMENTATION IN THE VIEW public class DriverDirectionsView {
 MapRenderer mapRenderer;


    GoogleApi gooogleApi; public void displayRoute(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final String query = constructQuery(currentLatLng, destinationLatLng, waypoints);
 
 final GoogleDirectionsResponseDTO response = googleApi.getDirections(query); 
 final GoogleRouteDTO googleRouteDTO = response.getRoutes().get(0); 
 final List<LatLng> firstLegLatLngs = getFirstLegLatLngs(googleRouteDTO);
 mapRenderer.addPurpleLine(firstLegLatLngs);
 
 final List<LatLng> otherLegLatLngs = getOtherLegLatLngs(googleRouteDTO);
 mapRenderer.addGrayLine(otherLegLatLngs);
 } …
 }
  5. IMPLEMENTATION IN THE VIEW public class DriverDirectionsView { …
 private

    List<LatLng> getFirstLegLatLngs( GoogleRouteDTO googleRouteDTO) {
 final GoogleLegDTO googleLegDTO = googleRouteDTO.getLegs().get(0);
 
 return getLatLngsFromLeg(googleLegDTO);
 }
 
 private List<LatLng> getOtherLegLatLngs( GoogleRouteDTO googleRouteDTO) {
 final List<LatLng> latLngList = new ArrayList<>();
 final List<GoogleLegDTO> legs = googleRouteDTO.getLegs();
 for (int index = 1; index < legs.size(); index++) {
 final GoogleLegDTO googleLegDTO = legs.get(index);
 final List<LatLng> latLngsFromLeg = getLatLngsFromLeg(googleLegDTO);
 latLngList.addAll(latLngsFromLeg);
 }
 
 return latLngList;
 } …
 }
  6. IMPLEMENTATION IN THE VIEW public class DriverDirectionsView { … private

    List<LatLng> getLatLngsFromLeg(GoogleLegDTO googleLegDTO) {
 final List<LatLng> latLngList = new ArrayList<>();
 for (GoogleStepDTO step : googleLegDTO.getSteps()) {
 final List<LatLng> decodedLatLngs = decodeOverviewPolyLineToLatLngs( step.getOverviewPolyline().getPoints());
 latLngList.addAll(decodedLatLngs);
 }
 return latLngList;
 }
 }
  7. PROBLEMS WITH THIS APPROACH ▸ API doesn’t fit well with

    our application needs ▸ Hard dependency on the Google API ▸ Difficult to reuse logic elsewhere ▸ Nothing is tested
  8. IMPLEMENTATION IN THE VIEW public class DriverDirectionsView {
 MapRenderer mapRenderer;


    GoogleApi gooogleApi; public void displayRoute(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final String query = constructQuery(currentLatLng, destinationLatLng, waypoints);
 
 final GoogleDirectionsResponseDTO response = googleApi.getDirections(query); 
 final GoogleRouteDTO googleRouteDTO = response.getRoutes().get(0); 
 final List<LatLng> firstLegLatLngs = getFirstLegLatLngs(googleRouteDTO);
 mapRenderer.addPurpleLine(firstLegLatLngs);
 
 final List<LatLng> otherLegLatLngs = getOtherLegLatLngs(googleRouteDTO);
 mapRenderer.addGrayLine(otherLegLatLngs);
 }
 }
  9. BUILDING A MORE USABLE OBJECT public class Route { private

    List<Leg> legs;
 public List<LatLng> getFirstLegLatLngs() {}
 public List<LatLng> getOtherLegLatLngs() {} 
 } public class Leg { private List<LatLng> latlngs; 
 public List<LatLng> getLatLngs() {}
 
 }
  10. TRANSLATING OUR DTO TO OUR DOMAIN public class RouteMapper {


    public static Route toRoute(GoogleDirectionsResponseDTO response) { 
 // Map pertinent GoogleDirectionsResponseDTO properties to // our Route domain object
 }
 }
  11. CURRENT PROGRESS: WITH DOMAIN OBJECT AND OBJECT MAPPER public class

    DriverDirectionView {
 MapRenderer mapRenderer; GoogleApi gooogleApi; 
 public void displayRoute(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final String query = constructQuery(currentLatLng, destinationLatLng, waypoints);
 
 final GoogleDirectionsResponseDTO response = googleApi.getDirections(query);
 final Route route = RouteMapper.toRoute(response);
 // displays the line in purple mapRenderer.addPurpleLine(route.getFirstLegLatLngs()); // displays the line in grey
 mapRenderer.addGrayLine(route.getOtherLegLatLngs());
 }
 }
  12. PROBLEMS WITH THIS APPROACH ▸ API doesn’t fit well with

    our application needs ▸ Hard dependency on the Google API ▸ Difficult to reuse logic elsewhere ▸ Nothing is tested
  13. USING THE APPLICATION SERVICE public class DriverDirectionView {
 MapRenderer mapRenderer;

    GoogleApi gooogleApi; 
 public void displayRoute(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final String query = constructQuery(currentLatLng, destinationLatLng, waypoints);
 
 final GoogleDirectionsResponseDTO response = googleApi.getDirections(query);
 final Route route = RouteMapper.toRoute(response);
 // displays the line in purple mapRenderer.addPurpleLine(route.getFirstLegLatLngs()); // displays the line in grey
 mapRenderer.addGrayLine(route.getOtherLegLatLngs());
 }
 }
  14. THE APPLICATION SERVICE public class DirectionsService {
 
 GoogleApi googleApi;


    
 public Route getDirections(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final String query = constructQuery(currentLatLng, destinationLatLng, waypoints);
 
 final GoogleDirectionsResponseDTO response = googleApi.getDirections(query);
 return RouteMapper.toRoute(response);
 }
 }
  15. FINAL RESULT public class DriverDirectionView {
 MapRenderer mapRenderer; DirectionsService directionsService;

    
 public void displayRoute(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 
 
 final Route route = directionsService.getDirections(currentLatLng,
 destinationLatLng, waypoints);
 // displays the line in purple mapRenderer.addPurpleLine(route.getFirstLegLatLngs()); // displays the line in grey
 mapRenderer.addGrayLine(route.getOtherLegLatLngs());
 }
 }
  16. PROBLEMS WITH THIS APPROACH ▸ API doesn’t fit well with

    our application needs ▸ Hard dependency on the Google API ▸ Difficult to reuse logic elsewhere ▸ Nothing is tested
  17. OBJECT MAPPER public class RouteMapperTest {
 
 @Test
 public void

    returnsSingleLeggedRouteForSingleGoogleLeggedRoute() { // given
 GoogleResponseDTOBuilder responseBuilder = new GoogleResponseDTOBuilder();
 // Build a response with a single route with single leg // and with a single step // when
 Route route = RouteMapper.toRoute(responseBuilder.build()); // then
 // Assert route object is constructed properly
 } …
 }
  18. DOMAIN OBJECT public class RouteTest {
 
 @Test
 public void

    returnsEmptyListWhenGetOtherLegsWithSingleLeg() { // given
 RouteBuilder routeBuilder = new RouteBuilder();
 // Construct a single legged route // when
 Route route = routeBuilder.build(); // then
 assertThat(route.getOtherLegs()).isEmpty();
 // more assertions
 }
 …
 }
  19. APPLICATION SERVICE public class DirectionsServiceTest {
 
 @Test
 public void

    returnsRouteWithSingleLegForApiErrors() {
 // given
 LatLng currentLatLng = new LatLngBuilder().build();
 LatLng desinationLatLng = new LatLngBuilder().build();
 GoogleDirectionsResponseDTOBuilder builder = new GoogleDirectionsResponseDTOBuilder();
 when(googleApi.getDirections(“some_query”)) .thenReturn(builder.build()); // when
 final Route directions = directionsService.getDirections(currentLatLng, desinationLatLng, Collections.<LatLng>emptyList());
 // then
 // assert values in route
 
 verify(googleApi, times(1)).getDirections("some_query");
 } 
 …
 }
  20. PROBLEMS WITH THIS APPROACH ▸ API doesn’t fit well with

    our application needs ▸ Hard dependency on the Google API ▸ Difficult to reuse logic elsewhere ▸ Nothing is tested
  21. THE PASSENGER VIEW public class PassengerDirectionsView {
 MapRenderer mapRenderer; DirectionsService

    directionsService; 
 public void displayRoute(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final Route route = directionsService.getDirections(currentLatLng, destinationLatLng, waypoints);
 // displays the line in purple mapRenderer.addPurpleLine(route.getFirstLegLatLngs()); // displays the line in grey
 mapRenderer.addGrayLine(route.getOtherLegLatLngs());
 }
 }
  22. UPDATING DOMAIN OBJECTS public class Route { private List<Leg> legs;


    public List<LatLng> getFirstLegLatLngs() {}
 public List<LatLng> getOtherLegLatLngs() {} 
 } public class Leg { private List<LatLng> latlngs; 
 public List<LatLng> getLatLngs() {}
 
 }
  23. UPDATING DOMAIN OBJECTS public class Route { private List<Leg> legs;


    public List<LatLng> getFirstLegLatLngs() {}
 public List<LatLng> getOtherLegLatLngs() {} public List<LatLng> getLatLngs() {}
 } public class Leg { private List<LatLng> latlngs; 
 public List<LatLng> getLatLngs() {}
 
 }
  24. FINAL RESULT public class PassengerDirectionsView {
 MapRenderer mapRenderer; DirectionsService directionsService;

    
 public void displayRoute(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final Route route = directionsService.getDirections(currentLatLng, destinationLatLng, waypoints); // displays the line in grey
 mapRenderer.addGrayLine(route.getLatLngs());
 }
 }
  25. INTERNAL API FOR DIRECTIONS {
 "route" : {
 "legs": [


    {
 "locations": [
 {
 "lat": 37.785392,
 "lng": -122.422636
 },
 {
 "lat": 37.785392,
 "lng": -122.432636
 }
 ]
 }
 ]
 }
 }
  26. INTERNAL API DTO public class ResponseDTO {
 RouteDTO route;
 }

    public class RouteDTO {
 List<LegDTO> legs;
 }
 
 public class LegDTO {
 List<LatLngDTO> locations;
 }
  27. THE APPLICATION SERVICE public class DirectionsService {
 
 GoogleApi googleApi;


    
 public Route getDirections(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final String query = constructQuery(currentLatLng, destinationLatLng, waypoints);
 
 final GoogleDirectionsResponseDTO response = googleApi.getDirections(query);
 return RouteMapper.toRoute(response);
 }
 }
  28. THE APPLICATION SERVICE public class DirectionsService {
 
 InternalApi internalApi;


    
 public Route getDirections(LatLng currentLatLng, LatLng destinationLatLng, List<LatLng> waypoints) {
 final String query = constructQuery(currentLatLng, destinationLatLng, waypoints);
 
 final RouteDTO response = internalApi.getDirections(query); 
 return RouteMapper.toRoute(response);
 }
 }
  29. OLD INTERNAL API FOR DIRECTIONS {
 "route" : {
 "legs":

    [
 {
 "locations": [
 {
 "lat": 37.785392,
 "lng": -122.422636
 },
 {
 "lat": 37.785392,
 "lng": -122.432636
 }
 ]
 }
 ]
 }
 }
  30. OLD INTERNAL API DTO public class ResponseDTO {
 RouteDTO route;


    } public class RouteDTO {
 List<LegDTO> legs;
 }
 
 public class LegDTO {
 List<LatLng> locations;
 }
  31. PREVIOUS ISSUES ▸ API doesn’t fit well with our application

    needs ▸ Hard dependency on the Google API ▸ Difficult to reuse logic elsewhere ▸ Nothing is tested
  32. WHAT WE HAVE NOW ▸ Domain objects that fit our

    application needs ▸ Flexible to API changes ▸ Reusable application logic ▸ Most things can be tested