Organization can make or break the maintainability of an application. With smaller applications, organization is more obviously apparent; however, as the application grows and as the number of application developers and front-end engineers producing code increases, the more confusing organization can become. In this post, we will go over some basic concepts for keeping applications organized so that finding relevant code is an efficient and systematic process.
Learn from Frameworks
JavaScript should be scoped in an efficient way.
Let's take a moment and consider the way Rails and Wordpress teams organize their projects. Chances are, you've probably worked with one or the other of these. Both frameworks have a set default structure.
When generating a Rails application, Rails handles much of the primary organizational needs for you. Because it's based on MVC, the default Rails setup includes a folder labeled "app", which contains model/view/controller folders. It also provides the "helpers" and "mailers", the controller extensions that help fill in the gaps between controllers and models.
Rails also generates a few other items at the same level as the "app" folder, such as configuration, logging, databases, temporary/caching, tests, and some other pieces. What is particularly interesting to this discussion are the app and public folders. The public folder is where static assets are served from, including any non-dynamic HTML, CSS, and JavaScript files.
When working with Wordpress, the naming and structure is far less obvious.
...the application is built for users...
Conventions are exposed by way of the documentation. In particular, developers likely work inside a theme, located in the wp-content/themes/ directory. This theme has an array of files with special names, primarily based on views. A functions.php
file acts as a "controller" of sorts, where a developer can put functions in order to separate them from the views. However, Wordpress themes often muddy the waters between logic and presentation. For instance, database queries are implicit by filename, but are often manipulated by altering the query before looping through the returned results. This manipulation does not abstract the same way that a controller would (and, of course, Wordpress is not anywhere near MVC, so we can't expect this type of organization).
Both of these frameworks are extremely popular, and both use their own implicit structuring. They absolutely require that developers understand the way the system works to be efficient. In our own applications, we should heed this organizational paradigm, and create a systematic approach for structuring applications. This plays out very differently for each individual application.
Building a Standard
The frameworks mentioned above do not require developers to define the structure; they are "pre-wired" to work a specific way.
One important reason robust frameworks often abstract these configurations and provide default structures is so that people will begin to develop a community standard based around the default structure.
Do not ignore standards when organizing your application!
If you are familiar with Rails, you can most likely look at a Github repository and know if it is a Rails app just by the folder structure-- Rails has a documented standard.
Do not ignore standards when organizing your application! It is highly likely that if you are organizing an enterprise-level application, you are dealing with modular, discrete service-based mini-applications. For example, it may be the case that you have multiple applications built with one or more different frameworks, or they may be rolled by hand and work in conjunction with one another, exposing APIs that provide hooks for the other services. Each of these discrete applications likely follow the standards of the framework it was built on; these standards exist because they are the way this framework was designed to work. If you try to change these standards within a discrete application, you likely end up wasting more time configuring than actually building a working application.
Uniformity of Connected Parts, Uniqueness of Discrete Parts
In practice, this means that the things that are exposed from one service to another should work in a uniform way, and the pieces that are internal to a service should work the way that is best for that particular service, probably driven by whatever framework or technology stack that the service runs on.
Remember, at the end of the day, the application is built for users, and users don't know or appreciate the separation of the discrete services.
They instead understand the application and experience as one piece, and that one piece will benefit primarily from uniformity at the top level.
So what needs to be uniform?
Routes
as the application grows...the more confusing organization can become.
Routes (URL structures) are one of the most important defining factors of how a web application works. It is important that your routes follow a uniform structure. For instance, when displaying a "user account" with a URL like /user/5
where 5
is the user's primary-key integer ID, you should not use a plural for another singular object, such as /widgets/16
. Instead, it should be /widget/16
. This not only helps developers by being consistent, but also provides clarity and uniformity for users. It doesn't matter what you choose for your route structure as long as it is consistent at the user-facing level.
API Syntax
This is one that is extremely important for internal development, and it's even more important for software product/service development where an API is exposed to the public. If your API calls have underscores as word separators, don't use a camelCase or dash-separated key in your API elsewhere. If you use the word "count" to mean "return this number of objects", don't use something like "count_per_page" to mean the same thing elsewhere in the same (or a related) API. This is sloppy API design. Develop a standard syntax and stick to it; note that this is often taken care of by well-executed route design in REST APIs.
Nouns, Verbs, and Labels
On a general level, when dealing with "foo widgets" (arbitrary objects or actions) within your application, make sure you use the same terminology across all of your applications. For example, while it may be common practice in Rails to use the "public" directory, you have control over what goes inside of it.
Don't use a "js" folder for one framework and a "scripts" folder for another framework.
Don't call data models "orange_items" in one framework and "OrangeItems" in another, unless the language or framework explicitly calls for this for a functional reason. Even then, make sure there is a consistent system and "grammar", and ensure the differences between the discrete services are well documented and justified. These kinds of cues and uniformity will greatly aid the understanding of classifications of objects across an application.
Mirror Routes, Folders, and Static Files
Mirroring routes to folders and files greatly helps keep an application organized. For instance, you may have folders for CSS, JavaScript, and Images inside your "public" or "static" folders. Creating a structure of folders that map to your routes, views, controllers, or other similar structures may help keep your CSS modularized. You can then use a tool like CodeKit to concatenate these files into a single minified file. Of course, you may also have a global.css
for rules that apply across the entire application. See below for an example (.rb is for Ruby, but this could go for any MVC's framework organization).
- root/ -- app/ --- models/ ---- foo.rb ---- bar.rb ---- baz/ ----- widget.rb --- views/ ---- global.html.erb ---- foo.html.erb ---- bar.html.erb ---- baz/ ----- widget.html.erb --- controllers ---- foo.rb ---- bar.rb - public -- CSS --- global.css --- foo.css --- bar.css --- baz/ ---- widget.css -- JS --- global.js --- foo.js --- bar.js --- baz/ ---- widget.js -- Images/ --- global/ ---- image.jpeg --- foo ---- image.jpeg --- bar ---- image.jpeg --- baz/ ---- widget/ ----- image.jpeg
One can quickly see that it wouldn't be difficult to find specific CSS, JavaScript, models, etc for a specific section of the application.
Routes (URL structures) are one of the most important defining factors of how a web application works.
The best way to think of this is to separate your files into "areas" of your site. For instance, perhaps you have an application that includes an "admin" area and a "profile" area. You could create a global.css
file to hold the objects/rules that apply to both areas (including a reset, some typography rules, colors, etc) and then create specific files that apply to the separate areas for content styling rules that aren't shared. This way, as a developer works on a specific page, they will stay in one or two files, and will know exactly where to find the right files.
... or, Name Your Static Files By Their Function
Another effective way to control your static files is to name them based on what they do. For instance, create a reset.css
, a typography.css
, a dimensions.css
, a colors.css
, etc. There are pros and cons to this method, particularly for CSS. It keeps your CSS focused on presentational rules; however, it will likely require that you repeat selectors across multiple files, reopening them to define different types of style rules. The only way to avoid this is to name your classes/IDs only by style rather than semantics, such as class="green-text five-columns medium-shadow"
. Otherwise, you will end up doing something like this:
In typography.css
.semantic-widget { color: #232323; text-shadow: 1px 1px #fff; font-size: 1.2em; line-height: 1.1em; font-family: sans-serif; font-weight: 300; }
In dimensions.css
.semantic-widget { width: 20%; margin: 0 2.5%; padding: 10px; }
Which of course isn't as DRY as possible.
Organization of smaller applications (with fewer of these "widget" objects) can still benefit from the separation by rule type.
However, if you have an application that has many different object types, application areas, etc, you should probably elect to separate your CSS files into areas (the mirroring strategy we discussed above).
Naming JavaScript files by their functions is a little more feasible, but only if you are taking advantage of event triggers (a pub/sub model). Consider the following example:
$.getJSON("/messages/all.json", function(data){ // do some stuff });
You may want to do quite a few things in this callback. You may want to send a notification to the user and update a list of messages. If you have your files separated by functionality, you may have a notifications.js
and a messages.js
file that handle these particular functions. Inside the callback for this JSON ajax call, you would "publish" an event to the rest of the application. Then inside your notifications and messages files, you can "subscribe" to the event, and respond accordingly.
in events.js
:
$.getJSON("/messages/all.json", function(messages){ $("body").trigger("received_messages", messages); });
in messages.js
:
$("body").on("received_messages", function(event, messages){ // iterate through messages and append to a list });
in notifications.js
$("body").on("received_messages", function(event, messages){ // send_notification may be a local function that allows you to send notifications // to the user, along with a notification type, an integer; for instance, in this // case, "1" may simply mean to style the notification as a "success" alert send_notification("Succesfully Received Messages!", 1); });
Another Note About Static Files
There are a few things to keep in mind about your static files. This article assumes that you will concatenate your static files in an appropriate manner. But what exactly is appropriate?
Consider Site Complexity
Chris Coyier recently claimed that no application needs more than three CSS files. In a distilled format, he claims that most sites that have one page, or many pages that are very similar, need one CSS file. Applications that have different "siloed" sections need one CSS file for global elements and one file for the specific sections. Finally, very complex sites need three files (global, section-specific, and one-off page css). Chris is referring to files that are loaded in the head (not necessarily what you develop in), so these would be your concatenated files.
So, why would you do this, you ask? Primarily to take advantage of browser cache.
Concatenating on the fly, for instance, doesn't allow you to take advantage of the browser's cache. The other advantage to this approach is only loading what you need. If you have an entire "admin" section that 95% of your users never see, they shouldn't have to download the CSS for that section.
Develop a standard syntax and stick to it.
Similar to CSS, JavaScript should be scoped in an efficient way.
The gains should always be managed carefully; if your javascript is small enough to justify concatenating into a single script, by all means, don't feel pressured to load modularized JavaScript in your end product.
Consider Usage Volume
As briefly mentioned above, if there is a negligible number of users hitting a specific page, don't put the CSS/JS into your global application static files. Administrative static files should also remain separate. If your application supports user accounts, consider discluding the static files for the unauthorized (marketing) side of the application. Once a user logs in, they have already made a commitment to take part in whatever your application provides. First impressions are everything, and loading a bunch of unused CSS and JavaScript will only degrade the performance of a first impression.
What Should Be Unique?
If you are indeed using a framework, use that framework's existing structure. If your framework doesn't have an existing structure, or a very simple structure (Sinatra and CodeIgniter are great examples of this), consider mirroring the structure of other components in the application. If you are beginning from the ground up, take a look at other projects people have completed in both the framework you are using and in other frameworks using the same language. For instance, if you're chosen framework works on MVC but doesn't have default configurations for the placement of these pieces, it's likely that there is a naming convention that uses the keywords Model, View, and Controller.
When it comes down to it, enterprise-level organization is about four primary things:
- Be consistent; develop internal, application-wide conventions.
- Follow framework/community conventions when possible and when necessary.
- Make logical, self-documenting choice.s
- Document your choices, and make sure inconsistencies within the system or with conventions are exposed and known by everyone working on the software.
Comments