8

I need to develop an API, the functions of the API are requests that call the service exposed by a server.

Initially the API worked like this:

class Server:
    def firstRequest(self, arg1, arg2):
        # block of code A
        async = Async()
        async.callFirstRequest(arg1, arg2)
        # block of code B

    def secondRequest(self, argA, argB, argC):
        # block of code A (identical to that of firstRequest)
        async = Async()
        async.callSecondRequest(argA, argB, argC)
        # block of code B (identical to that of firstRequest)

class Async:
    def callFirstRequest(self, arg1, arg2):
        doFirstRequest(arg1, arg2)

    # run the real request and wait for the answer
    def doFirstRequest(self, arg1, arg2):
        response = client.firstRequest(arg1, arg2)

    def callSecondRequest(self, argA, argB, argC):
        doSecondRequest(argA, argB, argC)

    # run the real request and wait for the answer
    def doSecondRequest(self, argA, argB, argC):
        response = client.secondRequest(argA, argB, argC)

server = Server()
server.firstRequest(arg1=1, arg2=2)
server.secondRequest(argA='A', argB='B', argC='C')

There was a lot of duplicated code and I don't liked the way it passed the arguments for the request. Because there are a lot of arguments so I wanted to extract them from the request and make something more parametric.

So I refactored in this way:

# using a strategy pattern I was able to remove the duplication of code A and code B
# now send() receive and invoke the request I wanna send
class Server:
    def send(self, sendRequest):
        # block of code A
        asynch = Async()
        sendRequest(asynch)
        # block of code B

# Request contains all the requests and a list of the arguments used (requestInfo)
class Request:
    # number and name of the arguments are not the same for all the requests
    # this function take care of this and store the arguments in RequestInfo for later use
    def setRequestInfo(self, **kwargs):
        if kwargs is not None:
            for key, value in kwargs.iteritems():
                self.requestInfo[key] = value

    def firstRequest(async)
        async.doFirstRequest(self.requestInfo)

    def secondRequest(async)
        async.doSecondRequest(self.requestInfo)

# Async run the real request and wait for the answer
class Async:
    def doFirstRequest(requestInfo):
        response = client.firstRequest(requestInfo['arg1'], requestInfo['arg2'])

    def doSecondRequest(requestInfo)
        response = client.secondRequest(requestInfo['argA'], requestInfo['argB'], requestInfo['argC'])  


server = Server()
request = Request()

request.setRequestInfo(arg1=1, arg2=2) # set of the arguments needed for the request
server.send(request.firstRequest)

request.setRequestInfo(argA='A', argB='B', argC='C')
server.send(request.secondRequest)

The strategy pattern worked, the duplication is removed. Regardless this I'm afraid to have complicated things, especially on regards of the arguments, I don't like the way I handle them, because when I look at the code does not appear easy and clear.

So I wanted to know if there is a pattern or a better and clearer way to deal with this kind of client side API code.

  • 1
    Have you considered asking about this on the Code Review Exchange? The users there are specialized in things like this. – Nzall Dec 18 '15 at 16:20
  • @NateKerkhofs Yes I have considered it, but then I choose Programmers because this is the board where to ask question about design and architecture as I read in the help section. In Code Review there's not even a tag for refactoring. – k4ppa Dec 18 '15 at 16:30
  • 2
    That is because the entire goal of CR is refactoring code. It is basically implied that all the questions in there are about refactoring code. Your code should already work, but apart from that, answers assume that you want your code checked out for bugs and refactoring. – Nzall Dec 19 '15 at 14:26
  • 2
    @NateKerkhofs - CR tends to take a very narrow focus with regards to a design review. As the OP is asking for guidance on the architecture of the solution, I think it's on-topic for this site. – GlenH7 Dec 21 '15 at 22:23
1

I would reconsider using a dictionary (hash, map, whatever your language calls a set of key/value pairs) for the arguments. Doing it that way makes it impossible for the compiler to check whether a caller has included all necessary values. It makes it difficult for the developer using it to figure out if they have all the arguments needed. It makes it easy to accidentally include something you didn't need and forget something you did need. And you end up having to put all the values into the dictionary when calling, and having to check the dictionary in every function to extract all the arguments, increasing overhead. Using some sort of specialized structure can reduce the number of arguments without reducing the compilers ability to check them and developers ability to see clearly what's needed.

  • Just as an add-on, if you are worried about ensuring all parameters are passed for a given request, then you can look at using abstract classes or interfaces (don't remember if that exists in python the same way as C# or Java), but that could become overkill depending on the current scope. The main benefit would be having clear definitions per request. – eparham7861 May 5 '18 at 18:03
0

I think your server API should have as many entries as required requests. Thus, any developer will be able to read the API easily (see lask routing as example).

To avoid duplication in code, you can use internal methods

0

API, architecture and pattern are all about communication and intent. Your second version looks simple enough to me and seems somewhate extensible too but my opinion (or even yours) is not what matters here.

Get feedbacks

Look at your software from outside in (not inside out). If is has a website, start from there. Expect to find easily one and only one obvious way of doing what your software is expected to do. Find someone in the hallway and ask for feedback about the usability.

Identify the main business object

Since software programing is always about making something new from two different things, having a Server that takes a Request seems sensible to me. The Server probably require configuration and has sensible defaults. You probably provide something like a singleton or factory to ease it's usage. The real intersting stuff happens in the Request. Your clients only have to make sure to build the proper Request object. Make your intent obvious and your business clear.

Be open for extension, but closed for modification

You could make it even simpler by encoding different behaviors using inheritance instead of multiple public methods in the Request object, pretty much like in the command pattern. This way, clients could also write their own requests, and new requests could be provided by plugins (using setuptools' entry points for instance) if required. This would also make sure that the Request object never become a god class, or have it's API changed if new features are added.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.