NoUML

Volodymyr Frolov
13 min readJun 20, 2018

--

After reading this article you will be able to draw Software Architecture diagrams that make sense.

Your coworkers would be able to read your diagrams and reason about your ideas even if they have never heard about NoUML. You will also be able to give a link to this article to your boss who is criticizing you for not using UML.

UML Criticism

UML is often criticized[1] for not giving us the right vocabulary for expressing our ideas about Software Architecture. On the one hand it is too vague to describe our ideas. On the other hand, it is excessively meticulous up to the point where it is just easier to implement something it in object-oriented language rather than draw a UML diagram for it.

Most UML tools allow you to convert a diagram to a code snippet in some object-oriented language and vice versa. This is a bad sign. It basically means that the resulting diagrams are almost as complex as their implementations. But we still need a language to express and discuss our ideas before any coding is even started. The goal of NoUML is to curb this complexity of Software Architecture diagrams. So, I promise you that no drawing tool would be able to generate a code template from a NoUML diagram. With NoUML you are going to deal with abstractions in their purest form.

You can think of UML as a microscope allowing you to examine how smallest parts of your system interact with each other. It could be an unnecessarily tedious process to examine an elephant by looking at various parts of it in the microscope though. In this analogy NoUML would be a spyglass allowing you to look at your system as a whole from a distance.

Abstractions

Software Architecture is all about abstractions and their relations to each other[2]. In NoUML abstractions with their relations are the first-class citizens. We don’t care what these abstractions correspond to in any particular programing language. Are these classes, objects, structs, methods, lambdas, modules, packages, containers, pure functions, entities, events, threads or microservices? We simply don’t care.

We also do not want to look deeper into abstractions’ inner structure. We can conceive these abstractions as atoms. We know they might have some inner structure but for us it is irrelevant. What matters for us though is how these abstractions rely to each other in our Architecture.

We will often refer to abstractions by their names but it is perfectly legit for an abstraction to be anonymous. You can define large bulks of anonymous code in modern programming languages after all. NoUML takes these anonymous abstractions into account as well.

Often you will be able to infer the meaning of some abstraction’s live by just looking on how the abstraction relies to other abstractions. Even if this abstraction doesn’t have a label on NoUML diagram we would still be able to infer what it is doing.

Relations

Abstractions are like atoms and there’s not much you can comprehend about them without looking into how they relate.

There are many kinds of relations of arbitrary arity between abstractions. In Software Architecture we care only about three of them and all three are binary. We call these relations: is, has and uses.

You can think of Software Design simply as an exhaustive collection of all is, has and uses relations in your system. For example: Apple is Fruit; Mary has Lamb, Client uses Service and so on. Then you can draw just a small slice of what is important to reason about your system on NoUML diagram and imply everything else.

There are some rules for how these relations interfere. You don’t have to memorize all of the rules to draw or understand NoUML diagrams, but I will still show you all of them here just to give you the taste of how these relations behave. Actually, in some cases, these rules could produce an infinite amount of relations (think of tail recursion, for example). You don’t have to draw all of them on a NoUML diagram. You only draw what is important to convey your ideas.

Now let’s examine the three relations one by one.

is

We think of is relation in a broader sense of generalization (see Liskov Substitution Principle) rather than just extends and implements kinds of inheritance[3]. In fact, a programing language might not have any inheritance mechanisms at all and yet there are going to be is relations for a system written in that language anyway.

We usually draw is relation on NoUML diagram as Venn diagrams:

Identity Rule: There always exists is relation between an abstraction and itself. We call this relation id unless there’s another name given to it. For example, if the abstraction is an object the id relation is often called this or self.

As you see here, an abstraction could rely to itself. Actually, an abstraction could rely to itself in more than just one way and not only as is. But there always exists this special self-referring relation that we call id.

In object-oriented programming it is the only is relation between an abstraction and itself. Which also means that in OOP if A is B and B is A then A and B are the same abstraction. But beware as in general this constraint does not hold.

is Transitivity Rule: For each is relation between A and B (you’ve heard it right, there might be more than one is relation between two abstractions, see for example the Diamond Inheritance[4] problem in C++), for each is relation between B and C, there must exist is relation between A and C.

Let’s look what happens if we plug the same abstraction, let’s call it Something, into A, B and C placeholders of this rule. We know that there exists id relation between Something plugged into A and the same Something plugged into B. There’s also the same id relation between Something plugged into B and plugged into C. The rule predicts that there must exist some is relation between Something plugged into A and C, so what that might be? Well, that’s just the same id relation we started with.

The rules might predict an existence of something that you know about and have a name for along with a certain place in your Architecture. But be careful as these rules might predict an existence of something completely unexpected and undesirable.

has

We think of has relation in a broader sense of association rather than just aggregation and composition[3].

We usually draw encapsulating abstraction as a rectangular box and put encapsulated abstractions inside of this box.

has Transitivity Rule: For each has relation between A and B, for each has relation between B and C, there must exist has relation between A and C.

We can draw the same abstraction on NoUML diagram more than once if it partakes in is and has relations at the same time and must be drawn as a circle and as a box.

Heritage Rule: For each is relation between A and B, for each has relation between B and C, there must exist has relation between A and C.

Ownership Rule: For each has relation between A and B, for each is relation between B and C, there must exist has relation between A and C.

Once again, you don’t have to draw Heritage Rule and Ownership Rule on your NoUML diagrams. You just need to know that these relations are always there.

uses

We think of uses relation as just an umbrella term for everything that doesn’t fit into is and has categories but important enough for us to be drawn. It could be a parameter of a method, a local reference, a call to a static method or new operator and so on.

We draw uses relation as just an arrow. The arrow points out to the abstraction that is being used.

Some times it is not easy to draw is and has relations properly as NoUML diagram might quickly get messy. In this case you can draw is and has relations also as an arrow with an appropriate label, just to keep your diagram cleaner.

uses Transitivity Rule: For each relation between A and B, for each relation between B and C, there must exist a relation between A and C. If no other rule applies, it must be uses relation.

As you can see, this transitivity rule is much broader than any other transitivity rule you saw so far. It applies for just any pair of consequent relations. Basically, if there exists any relation between A and B called f, and another one between B and C called g, there must always exist a relation between A and C. If this Transitive Relation doesn’t have any other name in your architecture, we call it “g after f”. This relation could be unintentional and unwanted, but it is still there.

Also id relation has this nice property that “f after id” is always the same relation as “id after f” and always the same relation as just f. We already saw an example of this property when we discussed that “id after id” is just id.

We draw an unwanted Transitive Relation as a dashed arrow.

Associativity Rule: For all subsequent relations f, g and h a transitivity relation “h after (g after f)” is the same relation as a transitivity relation “(h after g) after f”.

You can see that for any sequence of relations f1, f2, … fn the kind of resulting transitive relation (is, has or uses) does not depend on the order in which you combine these relations.

Examples

Now, when we are familiar with NoUML basics, let’s look at some examples.

3-Tier Architecture

Here you can see a NoUML diagram for 3-Tier architecture[5]:

Even though there are no labels, you can still guess that the first dot corresponds to Presentation Tier (as it uses middle abstraction), the second dot corresponds to Business Tier and the last dot corresponds to Data Tier (it is used by middle abstraction but doesn’t use anything else in return).

Now consider the following NoUML diagram with some additional uses relations:

It can’t be possibly a diagram for a well-crafted 3-Tier Architecture. This becomes obvious if you draw all Transitive Relations:

First problem on this diagram is that Data Tier uses Presentation Tier. This is definitely not what we originally intended to achieve. We could eliminate this relation by making Data Tier passive i.e. removing a relation from Data Tier to Business Tier.

Second problem is that Presentation Tier uses Data Tier. This is also undesirable as it causes high coupling, we have to revise Presentation Tier each time we replace our Database. We could eliminate this relation by splitting Business Tier into two abstractions: Interface and Implementation. There going to be is relation from Implementation to Interface to prevent Transitivity Rule from generating unwanted relations from Presentation to Data Tier.

Third problem is that there still exists uses relation from Business Implementation to Presentation. Presentation uses Business Interface now so Transitivity Rule says that there must be a uses relation from Business Implementation to Business Interface. This is a different relation than is relation that we already discussed.

Let’s say Business Tier must notify Presentation about some events. To eliminate the unwanted relation, we must introduce a new abstraction, let’s call it Notification Service. Presentation uses Notification Service by subscribing to events in it and Business Implementation uses Notification Service by publishing notifications now.

As you can see here at the resulting diagram there are no unwanted Transitive Relations left:

Model View Presenter

Now let’s look at MVP Pattern[6] shown on the following diagram:

Are there any Transitive Relations missing on this diagram? Yes, actually there are two potentially missing relations on this diagram: “emit user events after update view” and “emit model updates after update model”. There are two possibilities actually. We already have one uses relation called listen between Presenter and Events Module. First possibility is that these two predicted transitive relations are exactly are the same relation as listen.

Second possibility is that these exist two separate unwanted transitive relations:

In this case that might even mean that Presenter is listening to some different events, not those than are emitted by View and Model. This diagram tells us that we have to be careful and make sure that View and Model emits events to the same place where Presenter is listening for them. Probably we might need some Abstract Factory[7] for creating Model, View and Presenter to guarantee that.

But how can listen relation be the same thing as emit after update? Do not let the misleading names on NoUML diagrams to trick you. When analyzing NoUML diagrams look at abstractions and their relations without paying too much attention to their labels. In this particular case the relation is more complex than just listening to some events. Presenter takes some actions about these events by updating View and Model so it needs to break endless cycles by filtering events about its own updates, for example. In case of MVP the name of this relation does not convey the whole story.

But can we split View and Model onto Interfaces and Implementations to break this unwanted transitive relation, just as we did in previous example? Most likely these two Interfaces are going to use Events Module anyway, unless of course there’s some Factory or Dependency Injection[8] mechanism for providing and properly configuring Events Module to these Implementations without touching the mentioned Interfaces.

Object-oriented programming languages provide this ability to split every class into Public Interface and Private Implementation so you can think of every class as of two abstractions. It helps to brake some transitive dependencies but you have to be careful about what is exposed in Public Interface. Sometimes it is not enough to have a Public Interface and a Private Implementation to break an unwanted transitive relation. In our case the NoUML diagram shows us something important that we don’t want to miss. So sometimes it is beneficial to think about a class as just a single abstraction even though we know it has public and private parts.

Singleton

Now let’s look at our final example. Let’s see how Singleton pattern is described by GoF in Design Patterns book[7]. We will see what issues this pattern has and how these issues were resolved by Dependency Injection.

Initial approach

Singleton Interface provides getInstance method for accessing its only Implementation and ensuring that there exists the only one instance of that Implementation.

Are there any transitive dependencies that aren’t shown here on this diagram? Actually, yes, there exists is relation between Singleton Implementation and Singleton Interface, also there exists getInstance relation between Singleton Interface and Singleton Implementation; thus, there must exist a transitive relation: “getInstance after is”.

We know that for every abstraction there also always exists id relation between the abstraction and itself. If the abstraction is an object we call it this. So, we want “getInstance after is” to be this relation between Singleton Implementation and itself. But there’s a problem. The id relation can only be “is” relation whereas “getInstance after is” must be “uses” according to uses Transitivity Rule. There must be some undesirable irregularity about “getInstance after is” than just simply referring to Singleton Implementation through this.

Exposed Singletonicity

So, now when we found the undesirable irregularity, let’s investigate it. Let’s look how Singleton is supposed to be used by other classes:

We added uses relation between some Client and Singleton Interface but now we have another undesirable transitive relation: “getInstance after uses”. Somehow getInstance doesn’t belong to Singleton’s public Interface. What would happen if we decide that Singleton class can no longer be a Singleton and remove getInstance method? It will break all Singleton’s users. Singletonicity is an implementation detail of Singleton class. It has to be encapsulated and hidden.

Encapsulated Singletonicity

Now let’s look at NoUML diagram of Singleton managed by a Dependency Injection Container:

On this NoUML diagram there are no undesirable transitive relations left. Implementation is just annotated with @Singleton annotation now, so its singletonicity is kept in secret from Interface clients. Dependency Injection Container obtains this knowledge in runtime through reflection so there’s even no need to show it here on the whole picture as it is transparent for both the Client and the Implementation. Now we can safely switch from Singleton to non-Singleton implementation without any impact on the Client.

Conclusions

NoUML is a powerful tool for expressing and analyzing ideas about Software Architecture. Unlike UML it allows us to take a helicopter view of a Software System and analyze it as a whole. It provides us a universal vocabulary applicable in a wide range of cases.

NoUML also helps us in early detection of undesirable irregularities in a proposed Software Architecture.

We barely scratched the surface in this article though. You might also want to look at other Software Architecture and Design Pattern and try to analyze them with using of NoUML.

Further reading

This is my first article in NoUML series. You can read the next one here: NoUML with Levels of Abstraction

References

[1] Martin Fowler. Is Design Dead? 2000.
[2] Grady Booch, Robert A. Maksimchuk, Michael W. Engle, Bobbi J. Young, Jim Conallen and Kelli A. Houston. Object-oriented analysis and design and with applications, 3rd ed., 2007. p 88, 96, 138.
[3] Grady Booch, Ivar Jacobson and James Rumbaugh. Unified Modeling Language Reference Manual, 2nd ed., 2004.
[4] Robert C. Martin. Java and C++. A critical comparison, 1997.
[5] Martin Fowler. Patterns of Enterprise Application Architecture, 2002. p 19–22.
[6] Andy Bower, Blair McGlashan. Twisting the Triad. The evolution of the Dolphin Smalltalk MVP application framework. 2000.
[7] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software, 1st ed., 1994. p 87, 127.
[8] Martin Fowler. Inversion of Control Containers and the Dependency Injection pattern, 2004.

--

--