- Business logic is software’s heart and have to be exposed properly.
- Business logic is pure: uses only objects that represents domain in domain-valid state.
- Client-side app is core of whole project, should be implemented as first.
- Server-side API development should be driven by client-side needs.
- Client-side and server-side are separated.
- Both layers implements MVC.
I want to focus on client-side layer, because it is most interesting part. All snippets in this post are copied from hexagonal.js’ hello-world project.
Architecture is build with:
- Use cases
- WebSockets, LocalStorage etc.
You’re probably familiar with use case term. If you have some experience with DCI (or figured it out different way) you probably know, that use cases can be represented as objects - and this is core idea.
class UseCase constructor: -> start: => @askForName() askForName: => nameProvided: (name) => @greetUser(name) greetUser: (name) => restart: => @askForName()
Our story is quite simple: we want to greet user that uses app - ask for his name and greet him using name. As you can see it uses only plain objects and don’t care about booting, GUI or storage.
This sample app has only one adapter, the most basic - GUI. Let’s have a look at code of GUI for just first step of UseCase - askForName.
GUI#showAskForName shows simple form and binds to click event of its confirm button. It has no idea about domain objects and doesn’t contain any logic.
class Gui constructor: -> createElementFor: (templateId, data) => source = $(templateId).html() template = Handlebars.compile(source) html = template(data) element = $(html) showAskForName: => element = @createElementFor("#ask-for-name-template") $(".main").append(element) confirmNameButton = $("#confirm-name-button") confirmNameButton.click( => @confirmNameButtonClicked($("#name-input").val())) $("#name-input").focus()
You probably wonder how GUI know what to present and how can it interact with our business logic. hexagonal.js uses Glue objects to glue those two layers:
class Glue constructor: (@useCase, @gui, @storage)-> After(@useCase, "askForName", => @gui.showAskForName()) After(@useCase, "nameProvided", => @gui.hideAskForName()) After(@useCase, "greetUser", (name) => @gui.showGreetMessage(name)) After(@useCase, "restart", => @gui.hideGreetMessage()) After(@gui, "restartClicked", => @useCase.restart()) After(@gui, "confirmNameButtonClicked", (name) => @useCase.nameProvided(name))
Ok, so this part can be hard, because your don’t know what
After means. It’s shortcut from YouAreDaBomb library, which can be described by following code:
After = (object, methodName, advice) -> originalMethod = object[methodName] object[methodName] = (args...) -> result = originalMethod.apply(object, args...) advice.apply(object, args...) result
So basically - it adds to original function additional behaviour. There are also
Around functions that let you prepend or surround original function with additional behaviour.
To make it all run we have to implement some booting code, that’ll build all required objects: domain, glue, gui and other adapters and start use case. Here’s an example from hello-world app.
class App constructor: -> useCase = new UseCase() gui = new Gui() glue = new Glue(useCase, gui) useCase.start() new App()