For some time now I've wanted to write about the fundamental concepts behind Pakyow. This is the first of a dozen or so posts covering many of the details and considerations that have made their way into the framework's design.
I also recorded a screencast where I ramble through roughly the same content. It's available on the Pakyow YouTube Channel and is a nice supplement for this post.
Here it is, the simplest app you can build in Pakyow:
require 'pakyow-core' Pakyow::App.define do routes do default do send 'hello' end end end.run
1 library, 3 dependencies, 9 total lines of code.
And yes, you can tweet it.
Save to a file (e.g.
app.rb) and run it:
See Installation for details on getting your environment setup.
The app server should start up at localhost:3000. This
app is nothing complex — it simply accepts a request at the default
/ path and returns
hello in the response body.
$ curl http://localhost:3000/ hello
Despite its simplicity, there are many things we can learn from this example.
Single Responsibility Libraries
You'll notice that we require
pakyow-core rather than
pakyow. This is
because we only want a portion of the functionality that Pakyow provides. Pakyow
consists of 8 different libraries, or gems, each introducing a separate concern.
pakyow gem requires all 8 of the gems. Since we know that this app
will only deal with routing, we choose the
pakyow-core gem. The
gem handles defining the app, routing requests to it, and sending responses.
Single responsibility gems let us start small and only include features in our app when we need them. This keeps apps as lightweight as possible while scaling to meet the requirements of more complex apps (more on this in future posts).
Apps That Grow With You
core, we define our app.
Pakyow::App.define do ... end
Pakyow is designed to allow every part of the app code to be specified right in
define block. Here we can not only define our routes, but configuration,
middleware, and more advanced features like mutators.
While this is an easy place to start, it does start to break down as an app grows larger. Pakyow offers ways to break things out as needed.
Pakyow often provides multiple ways to accomplish the same task. You choose between them based on your needs. For a simple app like this, specifying the routes elsewhere creates more friction than just doing it inline. When the app grows, it's easy to refactor these routes into their own files.
This design decision provides a clear starting point while providing ways to organize code in a larger codebase. You can take it as far as you need to.
Now that we've defined an app, let's take a closer look at our routes.
default do send 'hello' end
In Pakyow, a route is responsible for routing a request to a bit of logic. Here
we see a single route, called
default. This route will match a request to
/, or the default path.
Pakyow couples the definition of the route with the work to be performed when the route is matched. You see this pattern in other frameworks, like Sinatra. We chose this approach to keep routing concerns in one place.
Consider if we had written the route like this:
default :SomeController, :some_action
SomeController#some_action would look like this:
class SomeController def some_action send 'hello' end end
The call to
send is a routing concern. Putting it in the controller (away
from routing) requires us to keep two bits of information in mind to understand
the entire request / response flow. This is bad.
A pattern I like to use when building Pakyow apps is for routes to contain only routing logic and delegate other concerns. Here's an example:
default do send SomeService.hello_or_goodbye end
Assume that the method on our service object makes some decision about whether it should say "hello" or "goodbye" and returns the appropriate message. We can look at this route and understand everything that happens from request all the way to response. All routing concerns are right in front of us.
Our default route simply sends back
hello in the response. This is done
send helper, which accepts a
IO object (e.g. a
File). It sets the content headers and body of the response, then immediately
halts execution and returns the response.
Send is one of a handful of helper methods that provide us with tools for dealing with different routing concerns. We'll look at other helpers in the future including error handlers, redirects, and rerouting.
Choosing an App Server
When your app server started up, it probably booted Webrick. This is the default server in Ruby and is part of the standard library. If Pakyow can't find another app server in its environment, it falls back to Webrick.
You can use a different app server by requiring it:
require 'pakyow-core' require 'puma' ...
Now the app will boot with Puma instead of Webrick.
Pakyow tries to make sane decisions when things are unspecified, but gives you the power to easily influence or change those decisions. This further reduces friction by letting you deal with things when you want to deal with them, rather than requiring many decisions upfront.
Start with conventions, allow configuration in the future.
We'll build on this 9 line app in the next post and discuss configuration, helpers, and middleware. Thanks for reading!