DDD, service and localization best practices?

I’m building HTTP API with DDD principles. The end goal is that the server runs on some chosen language X but the clients can use what ever language the software supports. Service has state, but HTTP is stateless.

First without language:

storage = new MyStorage(params) // Can be anything from FS based to SQL, etc
service = new TODO(storage)
server = new build(service)
server.run()

build class is something like:

class build
  server: HTTPBase

  func build(service) 
    server.POST('/add', 
      func(req, resp)
        what = req.Get("param")
        res = service.DoSomething(what)
        if res == "Y"
          resp.Write("Everything went fine")
          return
        resp.Write("Oh noes!")
    )

    return server

Let’s add a language (the wrong way with global state):

defaultLanguage = "eng"
storage = new MyStorage(defaultLanguage, params) 
service = new TODO(defaultLanguage, storage)
server = new build(service) // Oh noes!
server.run()

class build
  service
  server: HTTPBase

  func getLanguageFromURL(req, resp)
    self.service.SetLanguage(req.GetFromURL('$language'))

  func build(service)
    self.service = service // Oh noes!

    server.BaseRoute('/$language')
    server.Middleware(self.getLanguageFromURL)

    server.POST('/add', // This is now /$language/add
      func(req, resp)
        what = req.Get("param")
        // Because we're setting service's language in the middleware 
        // this becomes a mess because some other user using some other 
        // language might just done something and results to client then 
        // getting the result in wrong language.
        // (= This changes service's global state, and explodes)
        res = self.service.DoSomething(what)
        if res == "Y"
          resp.Write(translate("Everything went fine"))
          return
        resp.Write(translate("Oh noes!"))
    )

    return server

So where to go?

Always spawn new service in middleware?

defaultLanguage = "eng"
server = new build(defaultLanguage)
server.run()

class build
  service
  defaultLanguage
  server: HTTPBase

  func serviceMW(req, resp)
    storage = new MyStorage(self.defaultLanguage, params) 
    service = new TODO(self.defaultLanguage, storage)
    service.setLanguage(req.GetFromURL('$language'))
    req.Context['service'] = service


  func build(defaultLanguage)
    self.defaultLanguage = defaultLanguage
    server.BaseRoute('/$language')
    server.Middleware(serviceMW)

    server.POST('/add', // This is now /$language/add
      func(req, resp)
        // Now we have one time only service which is destroyed after request is complete
        service = req.Context.Get('service')
        what = req.Get("param")
        
        res = service.DoSomething(what)
        if res.X == "Y"
          resp.Write(translate("Everything went fine"))
          return
        resp.Write(translate("Oh noes!"))
    )

    return server

This is nicely isolated but starting the service might be really slow and eat lot of resources depending what it does.

Clone/copy the service in middleware?

defaultLanguage = "eng"
storage = new MyStorage(defaultLanguage, params) 
service = new TODO(defaultLanguage, storage)
server = new build(service)
server.run()

class build
  service // main instance which gets cloned for each request
  server: HTTPBase

  func serviceMW(req, resp)
    // Create new instance
    service = self.service.clone() 
    service.setLanguage(req.GetFromURL('$language'))
    req.Context['service'] = service

  func build(service)
    // Create new instance
    self.service = service.clone()

    server.BaseRoute('/$language')
    server.Middleware(serviceMW)

    server.POST('/add', // This is now /$language/add
      func(req, resp)
        what = req.Get("param")

        // Cloned in middleware and destroyed after this request
        service = req.Context.Get('service')

        res = service.DoSomething(what)
        if res == "Y"
          resp.Write(translate("Everything went fine"))
          return
        resp.Write(translate("Oh noes!"))
    )

    return server

Adds isolation and removes possible startup slowdowns and resource hogs as they’re done beforehand.

Run a pool (array) of services for each language?

class build
  service[language] // service["eng"], service["fin"], ..

There’s a lot of languages so it increases resource consumption. There could be also a mechanism that spawns service for a specific language when it’s requested for the first time. Also service’s that have been idle for N hours/minutes could be removed.

Add language parameter to service’s and repository’s functions?

All Service.DoSomething(param1) becomes Service.DoSomething(language, param1).

Services/repositories never return only strings but some sort of translatable objects?

So you have something like:

result = service.DoSomething(param1)
actualResult = result.Translate(myChosenLanguage)

What is the DDD way of handling localization? Am I missing any options?

Go to Source
Author: raspi