Facade design pattern

The next pattern we'll see in this chapter is the Facade pattern. When we discussed the Proxy pattern, you got to know that it was a way to wrap an type to hide some of its features of complexity from the user. Imagine that we group many proxies in a single point such as a file or a library. This could be a Facade pattern.

Description

A facade, in architectural terms, is the front wall that hides the rooms and corridors of a building. It protects its inhabitants from cold and rain, and provides them privacy. It orders and divides the dwellings.

The Facade design pattern does the same, but in our code. It shields the code from unwanted access, orders some calls, and hides the complexity scope from the user.

Objectives

You use Facade when you want to hide the complexity of some tasks, especially when most of them share utilities (such as authentication in an API). A library is a form of facade, where someone has to provide some methods for a developer to do certain things in a friendly way. This way, if a developer needs to use your library, he doesn't need to know all the inner tasks to retrieve the result he/she wants.

So, you use the Facade design pattern in the following scenarios:

  • When you want to decrease the complexity of some parts of our code. You hide that complexity behind the facade by providing a more easy-to-use method.
  • When you want to group actions that are cross-related in a single place.
  • When you want to build a library so that others can use your products without worrying about how it all works.

Example

As an example, we are going to take the first steps toward writing our own library that accesses OpenWeatherMaps service. In case you are not familiar with OpenWeatherMap service, it is an HTTP service that provides you with live information about weather, as well as historical data on it. The HTTP REST API is very easy to use, and will be a good example on how to create a Facade pattern for hiding the complexity of the network connections behind the REST service.

Acceptance criteria

The OpenWeatherMap API gives lots of information, so we are going to focus on getting live weather data in one city in some geo-located place by using its latitude and longitude values. The following are the requirements and acceptance criteria for this design pattern:

  1. Provide a single type to access the data. All information retrieved from OpenWeatherMap service will pass through it.
  2. Create a way to get the weather data for some city of some country.
  3. Create a way to get the weather data for some latitude and longitude position.
  4. Only second and thrird point must be visible outside of the package; everything else must be hidden (including all connection-related data).

Unit test

To start with our API Facade, we will need an interface with the methods asked in acceptance criteria 2 and acceptance criteria 3:

type CurrentWeatherDataRetriever interface { 
  GetByCityAndCountryCode(city, countryCode string) (Weather, error) 
  GetByGeoCoordinates(lat, lon float32) (Weather, error) 
} 

We will call acceptance criteria 2 GetByCityAndCountryCode; we will also need a city name and a country code in the string format. A country code is a two-character code, which represents the International Organization for Standardization (ISO) name of world countries. It returns a Weather value, which we will define later, and an error if something goes wrong.

Acceptance criteria 3 will be called GetByGeoCoordinates, and will need latitude and longitude values in the float32 format. It will also return a Weather value and an error. The Weather value is going to be defined according to the returned JSON that the OpenWeatherMap API works with. You can find the description of this JSON at the webpage http://openweathermap.org/current#current_JSON.

If you look at the JSON definition, it has the following type:

type Weather struct { 
  ID   int    `json:"id"` 
  Name string `json:"name"` 
  Cod  int    `json:"cod"` 
  Coord struct { 
    Lon float32 `json:"lon"` 
    Lat float32 `json:"lat"` 
  } `json:"coord"`  
    
  Weather []struct { 
    Id          int    `json:"id"` 
    Main        string `json:"main"` 
    Description string `json:"description"` 
    Icon        string `json:"icon"` 
  } `json:"weather"` 

  Base string `json:"base"` 
  Main struct { 
    Temp     float32 `json:"temp"` 
    Pressure float32 `json:"pressure"` 
    Humidity float32 `json:"humidity"` 
    TempMin  float32 `json:"temp_min"` 
    TempMax  float32 `json:"temp_max"` 
  } `json:"main"` 

  Wind struct { 
    Speed float32 `json:"speed"` 
    Deg   float32 `json:"deg"` 
  } `json:"wind"` 

  Clouds struct { 
    All int `json:"all"` 
  } `json:"clouds"` 
  
  Rain struct { 
    ThreeHours float32 `json:"3h"` 
  } `json:"rain"` 
  
  Dt  uint32 `json:"dt"` 
  Sys struct { 
    Type    int     `json:"type"` 
    ID      int     `json:"id"` 
    Message float32 `json:"message"` 
    Country string  `json:"country"` 
    Sunrise int     `json:"sunrise"` 
    Sunset  int     `json:"sunset"` 
  }`json:"sys"` 
} 

It's quite a long struct, but we have everything that a response could include. The struct is called Weather, as it is composed of an ID, a name and a Code (Cod), and a few anonymous structs, which are: Coord, Weather, Base, Main, Wind, Clouds, Rain, Dt, and Sys. We could write these anonymous structs outside of the Weather struct by giving them a name, but it would only be useful if we have to work with them separately.

After every member and struct within our Weather struct, you can find a `json:"something"` line. This comes in handy when differentiating between the JSON key name and your member name. If the JSON key is something, we aren't forced to call our member something. For example, our ID member will be called id in the JSON response.

Why don't we give the name of the JSON keys to our types? Well, if your fields in your type are lowercase, the encoding/json package won't parse them correctly. Also, that last annotation provides us a certain flexibility, not only in terms of changing the members' names, but also of omitting some key if we don't need it, with the following signature:

`json:"something,omitempty" 

With omitempty at the end, the parse won't fail if this key is not present in the bytes representation of the JSON key.

Okay, our acceptance criteria 1 ask for a single point of access to the API. This is going to be called CurrentWeatherData:

type CurrentWeatherData struct { 
  APIkey string 
} 

The CurrentWeatherData type has an API key as public member to work. This is because you have to be a registered user in OpenWeatherMap to enjoy their services. Refer to the OpenWeatherMap API's webpage for documentation on how to get an API key. We won't need it in our example, because we aren't going to do integration tests.

We need mock data so that we can write a mock function to retrieve the data. When sending an HTTP request, the response is contained in a member called body in the form of an io.Reader. We have already worked with types that implement the io.Reader interface, so this should look familiar to you. Our mock function appears like this:

 func getMockData() io.Reader { 
  response := `{
    "coord":{"lon":-3.7,"lat":40.42},"weather : [{"id":803,"main":"Clouds","description":"broken clouds","icon":"04n"}],"base":"stations","main":{"temp":303.56,"pressure":1016.46,"humidity":26.8,"temp_min":300.95,"temp_max":305.93},"wind":{"speed":3.17,"deg":151.001},"rain":{"3h":0.0075},"clouds":{"all":68},"dt":1471295823,"sys":{"type":3,"id":1442829648,"message":0.0278,"country":"ES","sunrise":1471238808,"sunset":1471288232},"id":3117735,"name":"Madrid","cod":200}` 
 
  r := bytes.NewReader([]byte(response)) 
  return r 
} 

This preceding mocked data was produced by making a request to OpenWeatherMap using an API key. The response variable is a string containing a JSON response. Take a close look at the grave accent (`) used to open and close the string. This way, you can use as many quotes as you want without any problem.

Further on, we use a special function in the bytes package called NewReader, which accepts an slice of bytes (which we create by converting the type from string), and returns an io.Reader implementor with the contents of the slice. This is perfect to mimic the Body member of an HTTP response.

We will write a test to try response parser. Both methods return the same type, so we can use the same JSON parser for both:

func TestOpenWeatherMap_responseParser(t *testing.T) { 
  r := getMockData() 
  openWeatherMap := CurrentWeatherData{APIkey: ""} 
 
  weather, err := openWeatherMap.responseParser(r) 
  if err != nil { 
    t.Fatal(err) 
  } 
 
  if weather.ID != 3117735 { 
    t.Errorf("Madrid id is 3117735, not %d
", weather.ID) 
  } 
} 

In the preceding test, we first asked for some mock data, which we store in the variable r. Later, we created a type of CurrentWeatherData, which we called openWeatherMap. Finally, we asked for a weather value for the provided io.Reader interface that we store in the variable weather. After checking for errors, we make sure that the ID is the same as the one stored in the mock data that we got from the getMockData method.

We have to declare the responseParser method before running tests, or the code won't compile:

func (p *CurrentWeatherData) responseParser(body io.Reader) (*Weather, error) { 
  return nil, fmt.Errorf("Not implemented yet") 
} 

With all the aforementioned, we can run this test:

go test -v -run=responseParser .
=== RUN   TestOpenWeatherMap_responseParser
--- FAIL: TestOpenWeatherMap_responseParser (0.00s)
        facade_test.go:72: Not implemented yet
FAIL
exit status 1
FAIL

Okay. We won't write more tests, because the rest would be merely integration tests, which are outside of the scope of explanation of a structural pattern, and will force us to have an API key as well as an Internet connection. If you want to see what the integration tests look like for this example, refer to the code that comes bundled with the book.

Implementation

First of all, we are going to implement the parser that our methods will use to parse the JSON response from the OpenWeatherMap REST API:

func (p *CurrentWeatherData) responseParser(body io.Reader) (*Weather, error) { 
  w := new(Weather) 
  err := json.NewDecoder(body).Decode(w) 
  if err != nil { 
    return nil, err 
  } 
 
  return w, nil 
} 

And this should be enough to pass the test by now:

go test -v -run=responseParser . 
=== RUN   TestOpenWeatherMap_responseParser 
--- PASS: TestOpenWeatherMap_responseParser (0.00s) 
PASS 
ok

At least we have our parser well tested. Let's structure our code to look like a library. First, we will create the methods to retrieve the weather of a city by its name and its country code, and the method that uses its latitude and longitude:

func (c *CurrentWeatherData) GetByGeoCoordinates(lat, lon float32) (weather *Weather, err error) { 
  return c.doRequest( 
  fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather q=%s,%s&APPID=%s", lat, lon, c.APIkey)) 
} 
 
func (c *CurrentWeatherData) GetByCityAndCountryCode(city, countryCode string) (weather *Weather, err error) { 
  return c.doRequest(   
  fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&APPID=%s", city, countryCode, c.APIkey) ) 
} 

A piece of cake? Of course! Everything must be as easy as possible, and it is a sign of a good job. The complexity in this facade is to create connections to the OpenWeatherMap API, and control the possible errors. This problem is shared between all the Facade methods in our example, so we don't need to write more than one API call right now.

What we do is pass the URL that the REST API needs in order to return the information we desire. This is achieved by the fmt.Sprintf function, which formats the strings in each case. For example, to gather the data using a city name and a country code, we use the following string:

fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&APPID=%s", city, countryCode, c.APIkey) 

This takes the pre-formatted string https://openweathermap.org/api and formats it by replacing each %s specifier with the city, the countryCode that we introduced in the arguments, and the API key member of the CurrentWeatherData type.

But, we haven't set any API key! Yes, because this is a library, and the users of the library will have to use their own API keys. We are hiding the complexity of creating the URIs, and handling the errors.

Finally, the doRequest function is a big fish, so we will see it in detail, step by step:

func (o *CurrentWeatherData) doRequest(uri string) (weather *Weather, err error) { 
  client := &http.Client{} 
  req, err := http.NewRequest("GET", uri, nil) 
  if err != nil { 
    return 
  } 
  req.Header.Set("Content-Type", "application/json") 

First, the signature tells us that the doRequest method accepts a URI string, and returns a pointer to the Weather variable and an error. We start by creating an http.Client class, which will make the requests. Then, we create a request object, which will use the GET method, as described in the OpenWeatherMap webpage, and the URI we passed. If we were to use a different method, or more than one, they would have to be brought about by arguments in the signature. Nevertheless, we will use just the GET method, so we could hardcode it there.

Then, we check whether the request object has been created successfully, and set a header that says that the content type is a JSON:

resp, err := client.Do(req) 
if err != nil { 
  return 
} 
 
if resp.StatusCode != 200 { 
  byt, errMsg := ioutil.ReadAll(resp.Body) 
  if errMsg == nil { 
    errMsg = fmt.Errorf("%s", string(byt)) 
  } 
  err = fmt.Errorf("Status code was %d, aborting. Error message was:
%s
",resp.StatusCode, errMsg) 
 
  return 
} 

Then we make the request, and check for errors. Because we have given names to our return types, if any error occurs, we just have to return the function, and Go will return the variable err and the variable weather in the state they were in at that precise moment.

We check the status code of the response, as we only accept 200 as a good response. If 200 isn't returned, we will create an error message with the contents of the body and the status code returned:

  weather, err = o.responseParser(resp.Body) 
  resp.Body.Close() 
 
  return 
} 

Finally, if everything goes well, we use the responseParser function we wrote earlier to parse the contents of Body, which is an io.Reader interface. Maybe you are wondering why we aren't controlling err from the response parser method. It's funny, because we are actually controlling it. responseParser and doRequest have the same return signature. Both return a Weather pointer and an error (if any), so we can return directly whatever the result was.

Library created with the Facade pattern

We have the first milestone for a library for the OpenWeatherMap API using the facade pattern. We have hidden the complexity of accessing the OpenWeatherMap REST API in the doRequest and responseParser functions, and the users of our library have an easy-to-use syntax to query the API. For example, to retrieve the weather for Madrid, Spain, a user will only have to introduce arguments and an API key at the beginning:

  weatherMap := CurrentWeatherData{*apiKey} 
 
  weather, err := weatherMap.GetByCityAndCountryCode("Madrid", "ES") 
  if err != nil { 
    t.Fatal(err) 
  } 
 
  fmt.Printf("Temperature in Madrid is %f celsius
", weather.Main.Temp-273.15) 

The console output for the weather in Madrid at the moment of writing this chapter is the following:

$ Temperature in Madrid is 30.600006 celsius

A typical summer day!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.84.169