Obvious is a clean software architecture. The goal is to make it easy to write software using TDD and to expose theintent of the software just by looking at the file structure. By using Obvious you also gain the benefit of having asystem that treats the external data sources and delivery mechanisms as implementation details that can be easilyreplaced as needed.
When looking at the app directory, it should be obvious what kinds of things the application does. This architecture highly values the "glance factor" of the application structure as well as being obvious in where to find things while working with the project.
Your application does not need to be a web app, api app, desktop app, or console app. It also doesn't need to be a MySQL, MongoDB, or SQLServer app. Your app is just a set of data structures and functionality related to those data structures. How your app delivers your app or stores data are implementation details.
Implementation details can and should change based on your implementation needs, your app logic shouldn't have to change when your implementation requirements do.
This is a TDD biased structure. Each layer and pattern is designed with testing in mind. More importantly, we designed this architecture so that testing would be easier for developers, which should lead to more testing and better software quality.
When given the choice between short term productivity or long term maintenance, we believe that the right decision is long term maintenance. Many decisions have been made that are counterintuitive from a short term productivity standpoint, but allow for much easier maintenance. Obviousness, framework independence, and testability all work together to make the day to day maintenance more enjoyable.
There are three well defined folders of each Obvious project, which define the three core divisions of a given project - app, delivery, andexternal. In the default Obvious structure, they exist as actual directories, but they are designed to be decoupled, so there is no reasonyou couldn't put the app, external, or delivery mechanisms into separate gems if that made sense for your project, but for demonstrationpurposes we assume them to all live in the same directory.
App is where the entities, actions, and data contracts of your application are housed.
Entities represent data in your system. They are fairly simple data structures that mostly just contain data and do validation onthe data they contain. Simple entities usually only need a shape method to make writing contracts easier, a populate method of populatingthe object, and a to_hash method for using the object elsewhere.
Actions are the use cases of the system. They are where most of the business logic of the system happens. They are single action objectsthat take in Jacks as constructor arguments to enable pluggable data sources.
Contracts define the data transport structures and perform format validation on the hashes. Data flows through the contracts tothe jacks and plugs and back. Because the contracts validate both the input and the output, invalid data structures raise an error andactions will fail. This ensures a standard mechanism for pluggable persistence.
Delivery is where you implement the delivery mechanism of your application itself. Delivery is where your app is integrated with externaldata sources and shown to the user. This means in simplest terms the UI, but it alsomeans creating concrete versions of the external objects, such as data jacks, and also calling the actions of the app itself.
If your system contains multiple delivery mechanisms - multiple web apps, api's, command line apps, etc. each project would live ina subdirectory of the delivery directory. In the example Obvious app below there is a sinatra app in delivery/web/, a sinatra json apiapp in delivery/api/, a purple_shoes desktop app in delivery/desktop/ and a commander cli app in delivery/cli/. You also could make your appand delivery directories into gems and have each delivery mechanism be its own directory.
External is where data transport between your app and various external infrastructure lives. This could be datbases, queues, orcaching layers connected to various data systems such as Redis, MongoDB, MySQL, the filesystem etc.
Jacks are the classes that inherit from Contracts. A contract is designed to wrap ajack method so that validation on the input and output hashes can be done. Jacks don't define a particularpersistance mechanism, but instead act more as a routing mechaism to various plugs.
Plugs are the objects that talk to external systems such as queues, databases, caching, 3rd party API's and so on. A single Jackcan have multiple plugs that plug into it. For example, you might have a BlogPostJack that has a BlogPostPlugForMySQL and aBlogPostPlugForMongo and a BlogPostPlugForFS. In that scenario you could swap out MySQL for Mongo or the Filesystem at any time.
The fundamental data structure of data transport between layers is a hash, ideally an immutable hash or struct. This is for both incoming and outgoing data from theeach layer. There are many benefits to this approach. The biggest benefit is that each layer stays decoupled from the other layersand that hashes act as messages passed between the layers. Immutable hashes or structs are preferable to mutable ones so that the messages stay in their original state.
Other benefits include:
Your delivery mechanism is only handed data, so you have less likelhoodof logic in your controllers or views (unless you do a bad thing). Your Jacks are only handed data, so contracts are able to validatetheir structure to ensure that Jacks and Plugs won't break your system. Also, Plugs only handing back data means your application isn'ttied to the data structures of a particular ORM or persistence library. Lastly, entities are populated with hashes, so it is easy tovalidate returned from the Jacks using the Entity.populate method.
Obvious Status - A twitter clone status update app. Itis designed as an example app, so you can follow the commits to get an idea of how an app would progress step-by-step.
Currently Obvious Status can be run as a web app, JSON api, command line app, or a desktop app using jruby. Data can be stored withJSON files, MySQL, MongoDB, or even the JSON api. You can pick from any of those options without changing your app folder code.
You can start using Obvious in Ruby by installing the ruby gem. Just 'gem install obvious'. To run it, call 'obvious generate' in a folder containing a "descriptors" folder with obvious yml descriptor files inside. The obvious generator will look at all of the action descriptors and will generate stub classes and pending tests for your project.
NOTE:descriptors are awesome for getting a new app folder structure going, but are basically ignored after your app is created. At some point in the future, the Obvious app generator will get a bit smarter and descriptors could be useful in an ongoing design process, but for now they make bootstrapping easier and that's it.
An example obvious descriptor file is available as a gist. It's a yaml file. It is designed to be for generate pseudocode and related tests. the c: elements are comments and the requires: elements determine what we think that method might need for that comment.
For a myriad of reasons, Ruby is not really the best choice to use for Obvious, but we have done our best to make it an enjoyable experience.
If you are interested in porting Obvious to another language, contact Brian on Twitter.
Uncle Bob Martin - The keynote at Ruby Midwest 2011 outlinedthe structure and ideas that became the Obvious Architecture. Uncle Bob's blog posts on The Clean Architecture and Screaming Architecture were also useful infurthering our understaanding of Clean Architecture as defined by Uncle Bob.
Alistair Cockburn - The Hexagonal Architecture ports and adapters concept directly influenced the idea of jacks and plugs in Obvious. In fact, the biggest reason they aren't named ports and adapters in Obviousis to avoid architecture confusion.