• No se han encontrado resultados

One of the great things about the Rails and ActiveRecord style of development is that developers can have the database and the models in their development environment. This means that the controllers, the views, and their associated tests can be created and run against only resources on the developer’s computer. When working with services,

ptg it is much more difficult to have a full environment on every developer’s machine. In

fact, this works against the service-oriented design goal of isolation.

The best way to design around this is to provide stubs, or mocks, for client libraries that have to call out to services. You can provide a single method that can be called to stub out a client library. That way, it can be set up in the development or test initializer in an application. The following examples show how to stub out service calls for the reading list and entry service libraries.

From the reading list, there is a stub for user IDs and the associated entry IDs they should return:

def self.stub_all_user_ids_with_ids(user_ids, ids) body = { :entry_ids => ids }.to_json response = Typhoeus::Response.new( :code => 200, :headers => "", :body => body, :time => 0.3) user_ids.each do |user_id| PauldixReadingList::Config.hydra.stub(:get, get_by_id_uri(user_id)).and_return(response) end end

The stub method takes an array of user IDs and entry IDs. First, it creates the

body of the response that is being stubbed. Then a response object is created that

contains that body. Finally, the user IDs are iterated through, with a call to

hydra.stub for each. This means that any call to hydra with a request for one of the

associated user reading list URIs returns this response object. Now a stub for the entry class needs to be created:

def self.stub_all_ids(ids)

body = ids.inject({}) do |result, id| result[id] = {

ptg :body => "something", :author => "whatevs", :published_time => Time.now} result end.to_json response = Typhoeus::Response.new( :code => 200, :headers => "", :body => body, :time => 0.3) PauldixEntries::Config.hydra.stub(:get, get_ids_uri(ids)).and_return(response) end

The Entry class stub works in very much the same way as the ReadinList stub.

It takes a list of IDs that are to be stubbed out. It creates a body that looks exactly like what a real service call would return. Then a response object is created and a call is made to hydra to stub out each of the entry ID URIs with the prebuilt response.

Finally, these stubs can be used so that service calls can be avoided but data will be available:

# calls to stub out

entry_ids = %w[entry1 entry2 entry3]

PauldixEntries::Entry.stub_all_ids(entry_ids)

PauldixRatings::RatingTotal.stub_all_ids(entry_ids)

PauldixReadingList::ReadingList.stub_all_user_ids_with_ids( ["paul"], entry_ids)

# regular calls to service clients reading_list = nil

PauldixReadingList::ReadingList.for_user("paul", :include => [:entry, :rating_total]) do |list|

reading_list = list end

ptg

# data can be accessed without ever calling out to a service reading_list.entries

The first lines of the example call the stub methods. The entry IDs that are being stubbed out are entry1, entry2, and entry3. The user ID that is being stubbed out

is paul. The call to configure the clients would still have to have occurred before the

calls to stub.

After the stubs have been set, the clients can be called. The call to HYDRA.run returns

the stubs and executes all the client code in the on_complete blocks, without ever hit-

ting the network. This example shows very specific stubs, but it’s a good idea to have sin- gle calls to the clients that will stub out any requests made. That way, the testing and development using these libraries can be done without the actual services running.

The one gotcha with stubbing out service request calls is that the stubs must accurately reflect what the services will return. For this reason, it is a good idea to keep the client libraries with the service code. That way, the client can be fully integration tested against a service when it is updated. A developer updating a service will already have to be able to run it locally. Thus, the developer should also run the client against the service.

Conclusion

Writing client libraries for services is a vital component of creating a successful service- based design. Good client libraries abstract away the complexity of business logic and scaling effort hidden behind a service wall. The key goals with client libraries are read- ability and ease of use of the API. Further, client libraries should not be responsible for making actual service calls. The best method is to give each client library the logic to form a request and send it off to a connection manager such as hydra. That way, other

libraries can queue requests at the same time.

Client libraries should be developed in tandem with the services they connect to. Services should be able to run multiple versions in parallel so that other applications that use the clients will not need to be upgraded in lock step. Then the new client libraries can be packaged up and deployed to applications after the services they con- nect to have been upgraded.

One final point about the client library examples in this chapter is that they have very similar code in their request logic. If services are standardized with conventions for how multi-gets, single requests, and data writes are made, much of this logic can be abstracted into a module that all the client libraries can use. This will cut down on code duplication and make the creation of more services easier.

ptg

Load Balancing