Root of Evil

Talking about ruby and other stuff.

Refactoring Wrappers

| Comments

Introduction

Very often in my everyday practice I faced a task when you need to create a communication between your own services or some 3rd party service that don’t have a implementation yet. So what is the best way to doing that?

Wrapper - it is a piece of code that will be responsible for communication between your application and external service.

Identicating problem

When you have a task that implies creation of wrapper you could came up with the easiest solution like:

1
2
3
4
5
6
7
8
9
10
class ServiceWrapper
  def self.server_rise(server)
    request = Net::HTTP.post_form(URI(Settings.service_url + "server_rise"), { server_name: server.name })
    if request['success']
      true
    else
      false
    end
  end
end

But as everyone knew requirements are changes often :) So this could be finished like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class ServiceWrapper
  def self.server_rise(server)
    request = Net::HTTP.post_form(service_url("server_rise"), { server_name: server.name })
    if request['success']
      true
    else
      false
    end
  end

  def self.add_platform(platform)
    request = Net::HTTP.post_form(service_url("add_platform"), { platform: platform.name})
    if request['success']
      request['response']
    else
      false
    end
  end

  def self.platform_rise(platform)
    request = Net::HTTP.post_form(service_url("platform_rise"), { platform: platform.name})
    if request['success']
      true
    else
      false
    end
  end

  def exceed_storage(server)
    request = Net::HTTP.post_form(service_url("exceed_storage"), { server_name: server.name })
    if request['success']
      request['response']
    else
      false
    end
  end

  def three_fourths_traffic(server)
    request = Net::HTTP.post_form(service_url("three_fourths_traffic"), { server_name: server.name })
    if request['success']
      request['response']
    else
      false
    end
  end

  def exceed_traffic(server)
    request = Net::HTTP.post_form(service_url("exceed_traffic"), { server_name: server.name })
    if request['success']
      request['response']
    else
      false
    end
  end

  def server_down(server)
    request = Net::HTTP.post_form(service_url("server_down"), { server_name: server.name })
    if request['success']
      request['response']
    else
      false
    end
  end

  private
  def self.service_url(url, options)
    URI(Settings.service_url + command)
  end
end

This doesn’t even looks like a object oriented approach, this is complete mess that could be extended became even worse. So what we could do about that?

Understanding problem

When you develop application especially application in ruby(You remember that this is Object Oriented language, right?) you should keep in mind what objects you create and how messages goes thought them. Let’s indicate our objects regarding this task scope:

  • Server
  • Platform
  • Notification

So basically we have:

  • Initiate Server creation ( isn’t this sounds like Object#new? )
  • Server down ( isn’t this changing the state of object ? )
  • Same for platform
  • Simple notifications that actually could be treated exactly how you think about your mailers

Solution

Now we see what objects we have, lets write simple implementations for Server.

1
2
3
4
5
6
7
8
9
10
class Wrapper::Server
  def new(server)
    @server = server
  end

  def self.save!
    response = Wrapper::Request.post("server_rise", server_name: @server.name)
    response.success?
  end
end

From your code it will looks like this Wrapper::Server.new(@server).save! name it as a wrapper is a bad idea, so you should probably name it with a service_name and add Wrapper and the end if necessary. Let’s see how request will looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
module Wrapper::Request
  class Error < StandardError; end

  def self.post(url, params = {})
    response = RestClient.post(Settings.service_url + url, params.to_json, content_type: :json, accept: :json)
    Wrapper::Response.new(response.body)

  rescue RestClient::Exception => e
    Wrapper::Response.new(e.response)
  rescue Errno::ECONNREFUSED => e
    raise Wrapper::Request::Error, "Service is down"
  end
end

And response

1
2
3
4
5
6
7
8
9
class Wrapper::Response
  def initialize(json_body)
    json_body = json_body
  end

  def success?
    !!json_body['response']
  end
end

This is just a proof of concept that will give your a direction in which you should move on. Now you will works with objects instead of hacks.

Conclusion

You should always treat your messages as a communication between objects. Even if coding in this way takes more time. But the more time your spend today the less time you will need to spend in future.

Comments