Learning to Architect Code: Part 1

Yoseph
5 min readSep 14, 2021

--

I’ve recently started working on redesigning an open source repository. In the past I’ve done work creating projects, writing algorithms or even small one off tools for development while in university or at work. All of this work has given me skills in different aspects of development, but as any seasoned software engineer knows, the world of software is VAST. Seriously vast. And there’s so many different sides of learning even within certain spaces. In my case, while I’ve worked in open sourced repositories for a few years now, I’ve never contributed to an overhaul of an existing open sourced repository that is run by a community.

And so I had to make sure whatever I was doing would be good. The repository, despite it’s flaws, had been built up over many years from many skilled developers contributing toward it and to just come in and blow it up with some piss poor mediocre code would be a huge mistake, and most importantly not help the community in any way. I felt like this open source repository was at an inflection point, where my contribution could save the project from a slow decline, or rapidly expedite the process.

So I started.

FYI the repository I’m working on is: MQTT.js

I wasn’t sure where to begin, either with planning, or by just jumping into the codebase and redesigning, and even then where would I start? The scale of the task quickly seemed overwhelming, so I knew that I needed to break it down somehow. I also wanted to get the community involved, so that my work would be peer reviewed and potentially attract other contributors on pieces of the code.

Creation Patterns

The first thing I did was explore creation patterns based on Object Oriented Design. This is a great resource someone referred me to: Creational Patterns | Object Oriented Design (oodesign.com)

The first thing I thought through was creational design patterns. There are a few choices. (You don’t have to worry about understanding these.)

  1. Singleton — Ensure that only one instance of a class is created and provide a global point of access.
  2. Factory — Creates objects without exposing the instantiation logic to the client.
  3. Factory Method — Defines an interface for creating objects, but let subclasses decide which class to instantiate and refers to the newly created object through a common interface.
  4. Abstract Factory — Offers the interface for creating a family of related objects, without explicitly specifying their classes.
  5. Builder — Defines an instance for creating an object but letting subclass decide which class to instantiate and allows a finer control over the construction process.
  6. Prototype — Specify the kidns of objects to create using a prototypical instance, and create new objects by copying this prototype.
  7. Object Pool — Reuses and shares objects that are expensive to create.

Of all the choices for creational design patterns, a factory makes a lot of sense, and it is also how the code currently works, in a way. The users would call:

const myClient = myLibraryFactory.connect(optionsObject)

Where myLibraryFactory is the ‘factory class’ that implements the instantation of the ConcreteObject. (The ConcreteObject would be the actual client that we want to create.) Here’s a UML Diagram outlining the Fatory Pattern implementation:

Factory Pattern | Object Oriented Design (oodesign.com)

Ok great. So we want to create an object this way…. Now what?

Well, there was something I didn’t like about the existing repository. It was using a factory already, but in a convoluted way. Consider the following diagram:

Design Pattern — Factory Pattern (tutorialspoint.com)

Here, we have a ShapeFactory that is called by the program to getShape() , and when it gets the shape it creates either a Rectangle, Square, or Circle, based on the options provided by the program. All of them will have a Shape Interface, but they are each instances of their own concrete classes.

With the repository I’m working with, they’re doing the same thing, kind of. One of the main points of parameterization for the repository is that the original designers said ok I want to build a factory, what will be my Concrete Product? It should be, if tightly following the Factory Pattern, wholly different “Concrete Products”, like a Square, or a Rectangle, or a Circle. But in this repository’s case, we are not providing wholly different Concrete Products. We are providing the same Concrete Product with different parameterizations within it. So while the old library is trying to look like this:

FactoryDemo (asks) → ConnectionFactory (creates) → TLS Connection | TCP Connection | Websocket Connection (implements) → MQTTConnection <<interface>>

It is inherently clunky because the connection is only one component of the Object. Ultimately we are giving back to the FactoryDemo application an interface for a MQTT Client, and the object is supposed to kind of look the same regardless of the parameterization of it’s connection. The connection is only an internal consideration, and in no way affects the external API or operations.

Specifically, each MQTT Connection via a different transport (TLS, TCP, or Websockets), can be a Factory Pattern since they each provide a generalized Interface to the MQTT Client, but the FactoryDemo application interacts with the MQTT Client, not the MQTT Connection. So this is a poor design.

The solution is to consider hiding the connections within a factory within the MQTT Client. So when MQTT Client is instantiated, it has a Factory Pattern for what we will call the Transport, and that will be a piece of the overall client.

Class Diagram of Nested Factory Pattern

Structure

Great, so the class has been instantiated. Now what? Let’s look at the overall structure of the library I’m working with presently.

Consider the dependency of the library as MQTT Packet, and treat the library itself as a dependency of User Application. The psuedo-UML structure diagram (I am not well-versed in UML), would look something like this:

MQTT.js Structure Diagram

Notably, the library has the ability to create two separate objects in two very different ways. The first, the Concrete MQTT.js Client, is created through the factory method connect() on the MQTT.js library. The second, the Concrete MQTT.js Store, is created through simple instantiation. In addition to that, the MQTT.js Client can take a dependency on the MQTT.js Store.

This concludes part one of this ongoing attempt for me to document my exploration into learning to architect code in the real world.

--

--

Yoseph
Yoseph

Written by Yoseph

Software Engineer passionate about the future of cities. Currently building libraries for Azure IoT.

No responses yet