Tackle two aspects of JavaScript development: modularity and ES6. With this practical guide, front-end and back-end Node.js developers alike will learn how to scale out JavaScript applications by breaking codebases into smaller modules. We’ll specifically cover features in ES6 from the vantage point of how we can leverage them to improve modularity in our programs. Learn how to manage complexity, how to drive it down, and how to write simpler code by building small modules.
If you’re a frontend developer or backend Node.js developer with a working knowledge of JavaScript, this book is for you. The book is ideal for semi-senior developers, senior developers, technical leaders, and software architects.
This book is part of the Modular JavaScript series.
Start with the book series launch announcement on Pony Foo
Participate in the crowdfunding campaign on Indiegogo
Amplify the announcement on social media via Thunderclap
Share a message on Twitter or within your social circles
Contribute to the source code repository on GitHub
Read the free HTML version of the book on Pony Foo
Purchase the book from O’Reilly on Amazon
Chapter 1
Module Thinking
As discussed in the Preface, complexity seems to be all around us while we’re working on software projects. So are abstractions, which keep complexity hidden away from us under rocks we don’t dare touch. These rocks are our interfaces to the rest of the world so that we can get away with hardly thinking about it. JavaScript is no exception here. On the contrary, as powerful as dynamic languages are, it is also that much easier, and even tempting, to write complex programs when we’re using them.
To get started, let’s discuss how we can better apply abstractions, interfaces, and their underlying concepts to the work we do. We then can minimize the amount of complexity we need to stare at when working on a project, a feature, a piece of functionality, down to the branches of a single function.
Introduction to Module Thinking
Embracing module thinking is understanding that complexity is, ultimately, inescapable. At the same time, that complexity can be swept under an interface, hardly to ever be seen or thought of again. But—and here’s one of the tricky parts—the interface needs to be well-designed so that its users don’t become frustrated. That frustration could even lead us to peering under the hood and finding that the implementation is vastly more complex than the poor interface we’re frustrated by, and maybe if the interface didn’t exist in the first place, we’d be better off in terms of maintainability and readability.
Systems can be organized granularly: we can split them into projects, made of multiple applications, containing several application-level layers, where we can have hundreds of modules, made up of thousands of functions, and so on. A granular approach helps us write code that’s easy to understand and maintain, by attaining a reasonable degree of modularity, while preserving our sanity. In Modular Granularity, we’ll discuss how to best leverage this granularity to create modular applications.
Whenever we delineate a component, there’s going to be a public interface that other parts of the system can leverage to access our component. The interface, or API, comprises the set of methods or attributes that our component exposes. These methods or attributes can also be referred to as touchpoints—the aspects of the interface that can be publicly interacted with. The fewer touchpoints an interface has, the smaller its surface area, and the simpler the interface becomes. An interface with a large surface area is highly flexible, but might also be a lot harder to understand and use, given the high amount of functionality exposed by the interface.
This interface serves a dual purpose. It allows us to develop new bits and pieces of the component, exposing only functionality that’s ready for public consumption while keeping private everything that’s not meant to be shared with other components. At the same time, it allows consumers—components or systems that are leveraging our interface—to reap the benefits of the functionality we expose, without concerning themselves with the details of how we implemented that functionality.
Robust, documented interfaces are one of the best ways of isolating a complicated piece of code so that others can consume its functionality without knowing any implementation details. A systematic arrangement of robust interfaces can be accrued to form a layer, such as service or data layers in enterprise applications. In doing so, we might be able to largely isolate and circumscribe logic to one of those layers, while keeping presentational concerns separate from strictly business- or persistence-related concerns. Such a forceful separation is effective because it keeps individual components tidy and layers uniform. Uniform layers, composed of components similar in pattern or shape, offer a sense of familiarity that makes them more straightforward to consume on a sustained basis from the point of view of a single developer, who over time becomes ever more used to familiar API shapes.
Relying on consistent API shapes is a great way of increasing productivity, given the difficulty of coming up with adequate interface designs. When we consistently leverage similar API shapes, we don’t have to come up with new designs every time, and consumers can rest assured that you haven’t reinvented the wheel every time. We’ll discuss API design at length over the coming chapters.