jeudi 6 février 2020

How to create a "subserver" or "subapplication" in Tornado?

Note 1: If this question is a duplicate, please link to the original question and close this one. I am not sure what the proper terminology for these concepts are. Note 2: If this question is better suited for softwareengineering.SE, please let me know so this question can be closed and I can ask it there. That site doesn't have a tornado tag though.

Hypothetical Scenario: Consider the following scenario, where the HTTP server at <base_url> is responsible for four endpoints:

  • <base_url>/cats/kittens
  • <base_url>/cats/tigers
  • <base_url>/dogs/puppies
  • <base_url>/dogs/wolves

which leads us to have the following routing table of URLSpec‘s:

server = tornado.web.Application([
 (r"/cats/kittens", KittenHandler, kitten_kwargs),
 (r"/cats/tigers", TigerHandler, tiger_kwargs),
 (r"/dogs/puppies", PuppyHandler, puppy_kwargs),
 (r"/dogs/wolves", WolfHandler, wolf_kwargs),
])

where the handlers, for the sake of completeness, could be defined as below:

class KittenHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("mew")

class TigerHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("ROAR")

class PuppyHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("woof")

class WolfHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("HOWL")

However, let's say that later I want to add handlers for lions, servals, jackals, pumas, coyotes, and foxes, and I want to have the pattern for dogs now additionally match "/canines/" (not just "/dogs/") and the pattern for cats now additionally match "/felines/" (not just "/cats/"). And I want to pass keyword arguments cat_kwargs only to all cat endpoints, and pass keyword arguments dog_kwargs only to all dog endpoints. What I think should happen is that there should be a "separation of concerns" between a "cat subserver" and a "dog subserver". Then I can make changes affecting all cat handlers in one fell swoop, and changes affecting all dog handlers in one fell swoop, and without changes for one affecting the other.

Question(s):

  1. Is having (in this case two) "subservers" or "subapplications", one for dogs and one for cats (and maybe in the future one for horses) a pattern or an anti-pattern? I.e. is it actually a good idea, and if not, what would be a better strategy?
  2. Depending on the answer to the first question, how could one best implement such a pattern using Tornado?

Hypothetical scenario continued:

As far as I understand, tornado.web.URLSpec is actually a subclass of tornado.routing.RuleRouter, and so the target in the (regex, target, kwargs) tuple does not have to be a subclass of tornado.web.RequestHandler, it could perhaps be something like a subclass of tornado.web.Application? So I think what I would like to implement might be like:

DogServer = tornado.web.Application([
  (r"/puppies", PuppyHandler, puppy_kwargs),
  (r"/wolves", WolfHandler, wolf_kwargs),
])

CatServer = tornado.web.Application([
  (r"/kittens", KittenHandler, kitten_kwargs),
  (r"/tigers", TigerHandlers, tiger_kwargs),
])

server = tornado.web.Application([
  (r"/cats", CatServer, **cat_kwargs),
  (r"/dogs", DogServer, **dog_kwargs),
])

Would the above even work? And second, even if it works, what would be the best way to implement a pattern like this? For example, I think it might be useful to somehow have KittenHandler and TigerHandler to be attributes of some CatProvider class (so people could easily modify it by swapping in their own custom KittenHandler's or TigerHandler's), or maybe subclasses of some CatHandler. For example, would something better than the above be the following?

class CatProvider(AnimalProvider):
    def __init__(self, kitten_handler=KittenHandler, tiger_handler=TigerHandler):
        self.kitten_handler = kitten_handler
        self.tiger_handler = tiger_handler

    kitten_regex = r"/kittens"
    kitten_kwargs = {...}

    tiger_regex = r"/tigers"
    tiger_kwargs = {...}

    def server(self):
        return tornado.web.Application([
            (self.kitten_regex, self.kitten_handler, self.kitten_kwargs),
            (self.tiger_regex, self.tiger_handler, self.tiger_kwargs),
        ])

class DogProvider(AnimalProvider):
    def __init__(self, puppy_handler=PuppyHandler, wolf_handler=WolfHandler):
        self.puppy_handler = puppy_handler
        self.wolf_handler = wolf_handler

    puppy_regex = r"/puppies"
    puppy_kwargs = {...}

    wolf_regex = r"/wolves"
    wolf_kwargs = {...}

    def server(self):
        return tornado.web.Application([
            (self.puppy_regex, self.puppy_handler, self.puppy_kwargs),
            (self.wolf_regex, self.wolf_handler, self.wolf_kwargs),
        ])

server = tornado.web.Application([
    (r"/cats", CatProvider(KittenHandler, TigerHandler).server(), **cat_kwargs),
    (r"/dogs", DogProvider(PuppyHandler, WolfHandler).server(), **dog_kwargs),
]) 

I quickly become confused about what should be the appropriate levels of abstraction for everything.

Aucun commentaire:

Enregistrer un commentaire