Clean Architecture Comprehensive Beginners Guide To Learn and Understand Clean Architecture by Wallace, Connor
Clean Architecture Comprehensive Beginners Guide To Learn and Understand Clean Architecture by Wallace, Connor
Structured Programming
Edsger Wybe Dijkstra discovered this in 1968. Structured programming
was the first to be adopted, but not the first paradigm to be invented. In this
paradigm, Dijkstra explains how the use of unrestrained jumps (goto
statements) can be harmful to the program structure. He then replaces the
jumps with blocks such as the if/then/else and the do/while/until . The
major purpose of the structured programming paradigm is to impose
discipline on direct transfer of control.
What led to the invention of the structured programming language was that
Dijkstra discovers that programming is becoming more and more difficult
to learn. The reason for this is because a program is often made up of
various details extremely technical for the human brain to grasp. Also,
when a programmer omits a small detail in his or her software development,
the software might appear okay at the first few trials but will shut down
after a while. As a result, programmers don't usually perform well in the
field. To help alleviate the issue, Dijkstra introduced the structured
programming that works with the mathematical discipline of proof .
Dijkstra's aim is to use the Euclidian hierarchy of postulates, theorems,
corollaries, and lemmas . He believes that programming will be a lot easier
when programmers use this mathematical hierarchy in their program
development. The hierarchy will help tie proven structures with codes so
that they will prove themselves to be correct.
However, he realized that for this to work, he will have to show the
technique for writing algorithms and basic proof. While carrying out his
investigation, Dijkstra found out that using goto for some programming
task prevent modules from being decomposed recursively into smaller and
smaller units. As a result, the programmer can't use the divide and conquer
approach necessary for proofs
Aside from the difficulty explained above, Dijkstra realized that other use
of goto does not have this problem. But these other uses are usually simple
tasks like selection, loops, iteration control structure like the if/then/else
and do/while . A module that makes use of these kinds of control structures
can be easily decomposed into smaller units.
It was with the knowledge of the above that Dijkstra discovered that when
controls are combined with sequential execution like the mathematical
hierarchy, the result will be very special. Although, this has first been
discovered and proven by two renowned programmers: Bohm and Jacopini.
These two programmers proved that all programs could be executed from
three structures: selection, sequence, and iteration. But structured
programming was shaped in such a way that the very control structure that
makes a program provable we're the same one combined together to form
the minimum sets of control from which programs can be built.
For the sequential statement, Dijkstra shows how this can be built by simply
using the enumeration process. This mathematical approach helps trace the
input of the sequence to the output of the sequence. Selection was also
handled by the process of iteration, in sequence, if both the input and output
produced the required mathematical result, the proof is a solid one.
However, the iteration process is a bit different. For iteration, Dijkstra
introduced the use of induction. He proved the case for 1 by enumeration.
He then went ahead to prove that if N is correct, then N + 1 is correct; this
again uses the enumeration method.
As computer language progresses, the goto approach continues to dwindle
while structured programming was embraced. Today, most modern
programming does not have goto . A good example is LISP.
Functional Decomposition in Structured Programming
While explaining how structured programming work, it was revealed that
with structured programming, modules could be easily decomposed into
smaller. This means that structured programming allows programmers to
decompose large scale modules into high-level functions. The high-level
functions can be taken one by one and decomposed into a lower-level
function, and the lower level function can be decomposed into smaller
units. Each of the decomposed functions can then be taken and represented
using the control structure of the structured programming.
As a result of this functional decomposition, disciplines such as structural
analysis and structured designs became very popular, especially in the 1970
and 80s. Programmers like Ed Yourdon, Larry Constantine, Tom DeMarco,
and Meilir Page-Jones were among those who made these techniques very
popular and widely accepted. With the introduction of these disciplines,
programmers can easily break down large modules into small provable
functions.
It is the ability to show a faulty unit of modules that made structured
programming a widely used paradigm today. This is also the reason why
modern programmers do not support the use of unrestrained goto
statements. At the level of software architecture development, structured
programming is still considered as one of the best paradigms until this day.
Object-Orient Programming
This paradigm was discovered in 1966, two years before the discovery of
the first paradigm. It came second because it was not immediately adopted.
The two programmers that invented this paradigm are Ole Johan Dahl and
Kristen Nygaard. These two programmers realized that the function call
stack frame in the ALGOL language could be moved to a heap. When this
happens, it will allow the local variables declared by a function to be still
valuable long after it returns. Here is the process necessary for this to
happen:
· The function will become a constructor for a class
· The local variables will become an instance variable
· The nested function becomes a method.
The process mentioned above led to the discovery of polymorphisms.
Object-orient programming can be summarized as a paradigm that imposes
discipline on indirect transfer of control. It works with the combination of
three significant processes; inheritance, encapsulation, and polymorphism.
Therefore an OO (short for object-orient) must support these three
processes. Let's examine these concepts one after the other.
Encapsulation
Object-orient programming provides a very flexible and practical
encapsulation of data and function. As a result of this, to carry out the
encapsulation process, a line can be drawn around a cohesive set of
functions and data. Outside the line, the data is hidden, and only some
functions are visible. This idea is not only limited to OO, but it is also used
in C programs as demonstrated below:
In the above programs, the user of point.h has no access to the members of
the struct Point . The users can call the makePoint () function and the
distance () function. This is a perfect example of encapsulation in a non-
OO language.
Inheritance
This is simply the declaration of a group of variables and functions within
an enclosing scope. Inheritance is also popular in C programming.
However, unlike in OO language, inheritance in C programming can only
be done manually. This is demonstrated in the program below:
A careful observation of the program below shows how the NamedPoint
data structure acts as if it was derived from the Point data structure. This is
so because the first two fields of the NamedPoint and the Point have the
same structure. Aside from this, NamedPoint can function like Point
because NamedPoint is a superset of Point and shares the same members
ordering like Point.
Although this type of program has long been before the invention of OO,
the inheritance program used then was mere tricky and not as detailed as
what we have in OO. This type of trickery is very popular with C++,
despite its popularity, its functions are convenient and elaborating as using
OO programming. In OO, multiple inheritances are very easy to
accomplish, but this is not possible with tricks in C++.
Indeed, OO doesn't offer something completely new; it actually made
masquerading a very convenient task. In encapsulation, no point can be
added to OO, in inheritance, we can add a half-point, but the most
elaborated among the three processes is the last one: polymorphism.
Polymorphism
To explain polymorphism, let's take a look at the above program in C
programming. In the program, the getchar() function reads from STDIN
while the putchar() function writes to the STDOUT , the question that
arises from these two actions is identifying the device STDIN and
STDOUT. However, the function performed by the two devices is known
as polymorphism.
Note: In the UNIX operating system, every IO device provides five
standard functions: open, close, read, write, and seek. The signature of each
of these functions is always very identical in every IO devices. The FILE
data structure contains five pointers to function.
If STDIN is defined as a FILE and if it points to the console data structure,
then the getchar() calls the function pointed to by the read pointer of the
file data structured to by the STDIN.
From the above explanation, it can be deduced that polymorphism is an
application of pointer functions. Like the two other processes,
polymorphisms have long been in used before the advent of OO
programming. Using a pointer to function in programming can be very
dangerous. This is because, before pointers can be used to point out
function, the programmer is expected to remember all the necessary
convention to call out all the function through the pointer. Since the
conventions are all manual, a programmer can easily forget any of the
conventions. Should this happen, it creates a bug very difficult to fix.
However, to avoid this kind of, OO programming already eliminates the
convention making it easy for programmers to use polymorphisms
smoothly. This great advantage is something not found in C programming.
The Power Of Polymorphism
Let's assume we already created a copy program and want to use our
program to copy data from the handwritten device into a speech synthesizer
device. What happens to our copy program when a new IO device is
created? What if changes occur? How do we change the copy program such
that it works perfectly with the new program?
It is in the above dilemma that polymorphism comes in. With
polymorphism, we don't need to recompile the program we already created.
This is because the source code of the copy program doesn't depend on the
source code of the IO device. Since the IO device can perform all the five
functions already highlighted in this section, the copy program will be able
to make use of all the five functions. Consequent to this, we can say that the
IO device has become a plugin for the copy program.
The UNIX operating system made our copy program a plugin. The reason
for this is what we all have come across. We all learned in the 1950s that
our programs are device-dependent . The plugins architecture was invented
to work with this type of program. Since the introduction of the plugin, it
has been used in almost every operating system. OO made it easy for the
plugin to be used anywhere with different devices.
Therefore, it can be concluded that OO programming made it possible for
the programmer to gain absolute control over every source code
dependency system through the use of polymorphism. With the OO device,
the architect can easily create a plugin device that allows high-level
modules to work independently of the low-level modules.
Functional Programming
Functional programming is a concept that started long before programming
itself. This system has been in use as far back as 1930 and strongly
influenced by the I-calculus invented by Alonzo Church. To explain how
this works, we will use the Squares of 25 integers program. In a language
similar to the Javascript, this program will be illustrated in this format.
The same program can be written in this form using the programming
language Clojure. Clojure is a derivative of Lisp and also a functional
program:
For those who are not too familiar with Lisp, this language might look too
strong. As a result, some comments will be added to help break it down a
bit. In the above program, it is obvious that println , take , map, and range
are all examples of functions. Functions are called in Lisp by putting them
in parentheses. This is why all the Lisp functions illustrated above are all in
parentheses.
The expression (fn [x] (* xx) ) written together with map is an anonymous
function. It calls on the multiple functions bypassing its input argument
twice or by computing with the square of its input.
To explain the whole program process, we will start from the deepest
function call
Notice the third category of the image above contains the transactional
memory. This is necessary for immutability because, since the mutable state
can easily cause concurrent problems in our system, it is advisable to use
the transactional memory to protect the mutable variables from these
problems.
The work of the transactional memory is similar to how a database is
recorded on a disk. Transactional memory is used to protect the variable
using the transaction or retry-based scheme.
Event Sourcing
Software storage and processing power have been receding nowadays.
Today, we have processors with thousands of byte RAM and the ability to
carry out unlimited instruction per. The beauty of this is that the greater the
storage capacity of a system, the faster the machine works, and the lesser
we need the use of mutability.
Take, for instance, the storage capacity of a banking industry system. The
system is expected to be able to maintain all the account balance of its
customers and also be able to mutate those account when a credit or debit
transaction is carried out.
Now let's imagine that instead of carrying out the actions as explained
above, the system stores only the transaction. When a customer wants to
know his or her account balance, all the transactions are added up to get
this. This method will require no mutability. This is the focus of event
sourcing. Event sourcing is simply the process of storing only the
customer's transaction without storing the statement of account. When the
statement of account is needed, all the programmer needs to do is to add up
the transactions. In event sourcing, the user doesn't necessarily have to wait
until the customer asks for his or her statement of account. A user can
update this every midnight or whenever he or she wants to. When the
transaction is requested, all the users have to do is to start the task from his
or her last program.
Summary
We can summarize this chapter by saying:
1. Solid principle
2. Single responsibility principle
3. Open-closed principle
Solid Principle
Every great software system started with clean codes. Clean codes are what
we used to build the structure already mapped out by the software architect.
We will use the same architecture for our house used in the first chapter of
the book. Let's assume after generating a very beautiful architecture of your
house, the builder started the construction with bad bricks. We will all agree
that no matter how great the house looks when it is completed, it will one
day crumble as a result of the bad bricks. In the same vein, assuming the
bricks are okay, the design is beautiful, but the builder still made a mess of
these things, our house will still not meet our taste. In programming, this is
where the concept of SOLID comes in.
The principle of SOLID provides a detail explanation of how functions and
can be arranged into various classes and how these classes are incorporated.
The use of the word "class" does not necessarily mean that this principle is
only applicable to object-orient programming. It covers all three
programming paradigm. A class in this principle means a coupled grouping
of data and functions. However, whether they are referred to as class or not,
all software programs have these types of data grouping, and it is this type
of grouping that the SOLID principle makes use of.
The Solid principle aims to create a mid-level structured program that can
perform these three tasks:
There are two important classes in the above program. The classes marked
with <I> are interfaces, those marked with <DS> are data structure. The
open head arrow heads using relationships while the close head arrows
represent inheritance or a relationship. The first most important thing to
notice with the dependencies is that they are all source code dependencies.
An arrow pointing from class A to class B means that the source code of
class A mentions something in class B, but the source code in class B does
not mention anything in the source A. This means that the Financial
DataMapper knows about Financial DataGateWay through the implemented
relationship, but the Financial DataGateWay doesn't know anything about
the Financial DataMapper. Another important thing to notice in this
diagram is each double line is crossed in only one direction. This means
that all the component relationships are unidirectional.
Summary
The Open-Closed Principle is one of the most important facets of clean
architecture. It makes the system very easy to extend with the barest
minimum of change. This chapter explains in detail two out of the five
principles under the SOLID. The next chapter will cover the remaining
three principles.
Chapter Four: Solid Principles
Two out of the five principles under SOLID principles have been explained
in the previous chapter, in this chapter, the remaining three principles will
be explained:
LSP: The Liskov Substitution Principle
ISP: The Interface Segregation Principle
DIP: The Dependency Inversion Principle
The Liskov Substitution Principle
Barbara Liskov developed this principle in 1988. The principle revolves
around interchangeable elements. It states that to build a software
programming from interchangeable parts, those parts must adhere to the
agreement that these interchangeable parts can be substituted one for the
other.
In explaining the principle, Barbara, who was the proponent, wrote that:
What is wanted here is something like the following substitution property: If
for each object o1 of type S, there is an object o2 of type T such that for all
program P defined in terms of T, the behavior of P is unchanged when o1 is
substituted for o2, then S is the subtype of T
We will be using a simple illustration to explain this. Let's assume that we
have a class of programs called License . This license class contains a
calcFee() method used in calling the Billing application. The license class
has two subclasses: the BusinessLicense and the PersonalLicense . These
subclasses use different algorithms to calculate the license. These kinds of
programs work to the rule of LSP. To function effectively, the Billing class
does not depend on any of its two subsets. Both the two subclasses can be
substituted for the license. When the LSP is violated, here are the problems
that can arise from the violation.
The Square/ Rectangle problem
To understand this problem, we will be using a screenshot of a diagram
showing the appearance of the problem.
In the above program, the square is not a proper substitute for the rectangle
because the height and width of the rectangle are independently mutable. In
contrast to this, to conform to the principle of LSP, the height and width of
the square must be changeable. Since the user of this program believes that
the square is communicating with the rectangle, this can easily be confused.
The only way to rectify the problem is to add a mechanism to the user. A
good example is an if statement. The addition of a mechanism will help
confirm if the rectangle is a square.
The Interface Segregation Principle
This principle admonishes software developers to stop depending on things
they don't need. It got its name from the diagram below
In the above program, several users are using OPS. Let's take, for instance,
that user 1 uses OPS 1, user 2, OPS 2, and user 3 uses OPS 3. The OPS
program is written in Java language. This means that the source code of
user 1 will also rely on OPS 2 and 3 despite not calling on them. As a result,
any change in the source code of either OPS 2 or 3 will force user 1 to
recompile and redeploy.
To solve this problem, we can segregate the principles, as shown in the
screenshot below. If the program is still written in the Java language, the
source code of user 1 will depend on u10ps and op1, but will not depend on
OPS. Hence, a change in the OPS will not affect user 1 or cause it to be
recompiled and redeployed.
Interface Segregation Principles and Language
From the program above, notice that the programming language affects the
program. Java language forces programmers to create a declaration that
must import, use, or include. The included declaration in source code
creates the source code dependencies that the user to recompile and
redeploy.
In other languages like Ruby and Python, these declarations do not exist.
Instead, they are inferred at source time. This is the primary reason
dynamically typed languages create systems that are more flexible and easy.
While Ruby and Python are examples of dynamically programming
language, Java is an example of statistical language.
This difference in how ISP works with program language led to the
conclusion that ISP is a language issue and not an architecture issue.
The Dependency Inversion Principle
This principle states that the most flexible system are those in which source
code dependencies refer only to abstraction and not to concretion. This
implies that in a statistical language like Java, the source code dependencies
like use include and import will only refer to source modules containing
interfaces, abstract classes, or some abstract kind of declaration; no
concrete modules will be depended on.
Dynamically typed languages like Ruby and Python are affected by the rule.
The source code module should not include the concrete module. But in this
type of language, it is a bit difficult defining what source code interface is.
In simple term, source code interface will include any module in which the
function being called are implemented.
However, because the string class in a programming language is always
very stable and tightly controlled, software developers and architecture do
not have to bother about frequently changing the modules in this program.
The volatile modules of our program are those we should avoid depending
on.
Stable Abstraction
Implementation is more volatile than the interface. This implies that every
change to an abstract interface leads to a change in its concrete interface.
However, changes to concrete implementation do not usually necessitate a
change in the interfaces that are implemented. As a result, all great
architects and developers try as much as possible to reduce the volatility of
the interface. This they do by looking for available means to add
implementation to the interfaces without affecting any changes in them.
This program implies that the stable software architecture is those that
avoid depending on the volatility of the interfaces. This favors the use of
stable abstract interfaces. This implication revolves around a couple of
some certain coding practices:
Do not refer to volatile concrete classes instead refer to abstract interfaces.
This rule cut across all languages, whether statistically or dynamically.
Also, the rule avoids the creation of the object and enforce them on the use
of Abstract Factories.
Do not derive from any of the volatile interfaces. This rule is deducted from
the previous rule, but there is an addition. This addition is that since
inheritance is very strong and stable in a statistically typed language, the
dependency will also be regular. But in dynamical language, inheritance is
not as strong as it is in statistically language.
Do not override concrete functions. Concrete functions usually require
source code dependencies. When these functions are overridden,
dependencies are not eliminated; instead, they are an inheritance. To control
these dependencies, make the concrete functions abstract by adding more
implementations.
Do not mention the name of anything concrete or volatile
Factory
To be able to comply with the rules mentioned above, the creation of
volatile interfaces requires some specialties. Caution is very paramount
because in all programming languages, to create an object, a source code
dependency is required on the definition of that object.
Most object-oriented language like Java uses an Abstract Factory to manage
unwanted dependencies.
The screenshot below shows the structure of the Abstract Factory.
In the above program, the Application uses Concrete ImpI through the
service interface. However, the interface must somehow create instances of
the Concrete ImpI to achieve this without creating a source code on the
Concrete ImpI; the Application calls the makeSvc method of the
ServiceFactory interface. This service is implemented by the
ServiceFactory ImpI class, which derives from ServiceFactory. The
implementation instantiates the Concrete ImpI and returns it to a service.
The curve line is an architectural boundary separating the concrete
interfaces from the abstract. The abstract interfaces include every high-level
business rules of the application, and the concrete component contains all
the implementation details that those rules manipulate.
In the diagram above, note that the flow of control crosses the cross line in
the opposite direction of the source code dependencies. The dependencies
of the source code are inverted against the flow of control. This is why the
principle is referred to as the Dependency Inversion Principle.
Concrete Component
In the image above, the concrete element contains only a single
dependency, so it violates the DIP. This is quite common with dependency
inversion. Violations like this cannot be rectified. They can only be
gathered into a small number of concrete elements and kept separately from
the system.
Summary
This chapter covers the remaining three principles under the SOLID
principle, from the explanation so far, we note that to create clean software,
our program must adhere to some principle. Failure to comply can lead to
problems too difficult to rectify. As we progress in other principles of
designs, especially the higher-levels principles, we will be using some of
the principles under the SOLID. The DIP will come up again and again and
become the most visible principle in architecture programs.
Chapter Five: Component Principles
In examining the principles under SOLID, it was mentioned that all these
principles only apply to bricks. They are used to arrange bricks into walls
and rooms. The next principle is a buildup on the SOLID. While SOLID
deals with bricks, COMPONENT principles on how to arrange rooms into a
building. This explanation is in relationship to our explanation of what
architecture is. The larger building is a product of smaller buildings
arranged to form the big edifice.
Software Component
A component is a unit of deployment. They are the smaller entities that are
used to create part of the system. The software components of some
programming languages are highlighted below:
· Java programming - jar file.
· Ruby - gem files.
· Net - DLLs
In compiled languages, the software component is the aggregation of the
binary data. In interpreted languages, they are the aggregations of source
files. In all languages, they are a granule of deployment.
A component can be linked together to form a single executable. They can
also be aggregated together into a single archive. A component can be
independently used as separate dynamical plugins. Examples of these
include jar, .exe, and dll. A well-developed component will always retain
these two abilities:
Ability to be independently deployable
Ability to be independently developable
History of Software Component
Before the advent of components, programmers controlled the memory
location and layout of their programs. Among the first line of codes that
was usually used is the origin statement. This is used to declare the address
of where the program will be loaded. A good example is the PDG -8
program illustrated below. In the program, a subroutine called GETSTR
was used. This is used to implement a string from the keyboard and then
input it in a string.
The *200 at the beginning of the program tells the compiler to generate
codes that will be loaded at the 200g address. This kind of program is novel
to today's programmers who do not have to bother themselves with where
the program will be loaded in the memory of the computer. But in those
days, programs are not relocatable.
How Library Functions are Located in Those Days
In the early days of programming, the library was saved in the source class
and not in the binary. To access this, programmers will have to join the
source code of the library function with the application code and then
compile them as a single code.
This approach became challenging because of the limited device space. Not
only this, devices in this period, was very slow, and getting a memory is
very expensive. Also, as the library file grows, compilers are forced to
make several passes at the source code, but as expected, the memory space
became too small to keep all the source codes.
As a result of the problem, the compiler is left with no option but to read
from the source code using the slow device. The longer the compiler, the
longer the time spent reading from the source code. This approach is not
only time consuming but also effort draining.
To control and shorten the time spent on source code, programmers separate
the source code of the application function from that of the library function.
Next, they created a symbol table for the function library. When they want
to run an application, they will load the binary function library and then
load the application function.
The solution works very well so long as the application could fit to address
0000g and 1777g. But along the line, the size of the application increases,
forcing programmers to split the application into two segments around the
library function.
The problem continues to grow as the size of the application increases. It
becomes clear that to rectify the issue, something entirely different has to be
done. This gave birth to relocatability.
Relocatability
The solution to the problem is relocatability. This approach is very simple.
The programmer only has to convert the compiler into an output binary
code that could be relocated in memory by a small loader. To run this, the
loader will be told where to load the relocatable code. The relocatable code
was instrumented with flags used to inform the loader which part of the
loaded data has to be selected and loaded at the address selected.
Also, the compiler was changed to emit the names of function as metadata
in the relocatable binary. If a program calls a library function, the compiler
will emit the name as an external reference . If a program defined a library
function, the compiler will emit the name as an external definition . Once
the compiler has emitted the names of the functions called, the loader can
easily link the external reference to the external definition.
This approach gave birth to linking loaders.
Linkers
The linker loaders allow a programmer to divide their works into small
compilable, and loadable units. This works well when both programs linked
together are very small. However, as programming progress, this approach
begins to have its own problem. Notable among its problems is that it
became too slow to work with. The larger the program, the more time the
linkers take to load. It could take as long as an hour to successfully link
programs.
To rectify this, the loading and the linking were separated into two phases.
The slow part that did the linking was developed into an application called
Linker . The output of the linker was a linked relocatable that a relocating
loader could load very easily.
From the explanation so far, the software component is the dynamically
linked file that can be plugged together at runtime. From our explanation
above, it is obvious that before the component can be run in a very casual
way, it took years of developing and creating new applications. The next
outline will cover all aspects of component cohesion.
Component Cohesion
Here we will be examining how components are grouped into classes.
Three principles are guiding the grouping of components:
REP : Reuse / Release Equivalent Principle
CCP: Common Closure Principle
CRP: Common Reuse Principle
Reuse/Release Equivalence Principle
Reuse granule is the same as the release granule.
The steady advancement in programming gave rise to the birth of some
managerial tools like the Maven, Leiningen, and RVM. These tools have
been greatly improved. Because, during the early days of programming, a
vast number of reusable components and component libraries were created.
Today, we are living in the age of software reuse.
The Reuse/ Release program states that people who want to reuse a
software component can only do that if the component is tracked to a
release process and given a release number. Without the release numbers,
there is no other way to ensure that all the reuse components are compatible
with each other. Software programmers are required to know when new
releases are coming and the changes the new release will bring. The release
process must produce the necessary notification and release documentation.
This will help the software user to know when and whether to add the new
release.
Put simply, from a programmer point of view, the classes and modules that
are grouped together to form a component must belong to a cohesive group.
Modules cannot be taken from random classes to form a component. All
modules in a component must share some intrinsic functionality.
This principle also implies that classes and modules that are grouped
together must be releasable together. The fact that they share similar release
numbers, names, and documentation should give some clues to the user or
programmer. Violation of this principle is easy to detect and could make the
developer look like an amateur.
The Common Closure Principle
This principle states that all the classes that change for the same reasons and
at the same should be gathered into a component while classes that change
at different times and for different reasons should be separated into different
components.
A careful observation of the rule will show that it shares great similarities
with the Single Responsibility Principle. The SRP states that a class should
not have multiple reasons to change; CCP states that a component should
not have multiple reasons to change. The only difference between the two
principles is SRP applies to classes while CCP applies to a component.
Maintainability is more important than reusability in most applications. As
a result, if there must be any changes in the code of an application, these
changes should occur in one component rather than spreading around all the
other components. When changes are limited to a single component,
redeployment and recompilement will be limited to that single component.
The components that are not affected does not need to be redeployed.
This principle prompts developers to gather together all the classes that are
likely to change for the same reason. It is these classes that we will use to
form our component.
The Common Reuse Principle
The principle states that do not force users of a component to depend on
things they don't need
This is another principle that helps us to determine which class of modules
to form a component. It states that classes and modules that will be reuse
together should belong to the same component. Classes are usually not
reused in isolation. Since CRP states that this type of class belongs to the
same component, it is expected to see modules that have lots of
dependencies on each other in a component.
Tension Resulting from Component Cohesion
From the brief explanation of these three component principles, it is
obvious that these three principles counter one another and will tend to fight
in a program. The first two principles, the REP and the CCP are all-
inclusive while the last CRP is an exclusive principle. The inclusive
principles are so because from their approaches, they tend to make a
component larger while the exclusive one makes a component smaller.
As a result of the tension between these principles, developers who focus on
any of the three will face some difficulties. For instance, focusing on the
REP and CRP will affect too many components when any changes are
made, in the same vein, focusing on the CCP and REP will produce too
many unneeded resources.
To rectify this, the architect will have to find a point of agreement among
all three principles. This position will also meet the stakeholders or users
requirements
Summary
Having explained all three principles of component cohesion, it can be
summarized that all three principles explain a complex variety of cohesion.
To choose the classes or modules to form components, the architect should
take note of reusability and readability. These two factors must be well
balanced in a component.
Chapter Six : Component Coupling
This chapter will focus on the next three principles of software components.
These principles focus on the relationship between components. In our
explanation, we will also be expatiating on the tension between
components, as seen in the previous chapter. We will explain the three
forces that impinge on the architecture of a component structure. These
forces include: political, technical, and volatility. This chapter will cover:
· Acyclic dependencies principles
· How cycle affects a component
· Breaking the circle
· Stability in a software component
· How to measure stability
· Stability abstraction principle.
The Acyclic Dependencies Principles
This principle allows no cycles in the component dependency principle. To
explain how it works, let's take some moment to think back to the time we
have already completed some things in our software. All the programs are
working fine before we shut down our systems and close for the day.
Unfortunately, when we came back the next day, we are greeted by faulty
programs and wonder how this happens. It is not uncommon for us to face a
situation similar to this, especially when working with a team of software
developers. The reason for this is simple: someone in the team has tampered
with your stuff and changed some of your programs.
This type of problem can keep evolving, especially when working with a
large team of developers. However, some decades ago, the
telecommunication industry came up with two solutions to this problem.
They are the Weekly Build, and the Acyclic Dependencies Principle
commonly referred to as ADP.
The Weekly Build
This solution is very common with average-sized projects. This solution
works as the name implies, all the developers in the team are to work
independently for the first four days in the week. On the fifth day, they
integrate their works together and build their change. The approach offers a
splendid opportunity for developers to work on private copies of codes for
the first four days. However, the disadvantage is usually on the workload on
the fifth day. Except for this, the approach is very okay.
One major factor that causes the disadvantage is the size of the program. As
the size of the program increases, integrating becomes more difficult to
achieve in a day. It sometimes spills on the second day. In the long run,
some managers might suggest running the program bi-week. This also gets
more difficult to work with as the programs increasing. This situation
eventually leads to a crisis. Integrating becomes harder to achieve, and the
developers are frustrated.
The solution to this is simple. The best way to go about it is to partition the
development component into releasable components. Once the component
is partitioned into releasable components, they become easy for the team of
developers to work with. When a unit or component is found working, the
developer releases it to other members of the team. Each member of the
team must modify this independently.
When another release is made available, all the developers in the team
decides on when to adopt it. If they all decide not to work with the new
release yet, they simply continue with the old one. This solution is very
simple and easy to work with. However, the problem with this is that there
can be no cycle in the dependency structure when this is violated; it can
lead to the problem we want to avoid at the onset. The image below
delineates this:
In the above diagram, the nodes are the components, while the directed
edges are the dependency structure. In the program, irrespective of the
structure you choose to work with, there are no cycles. This structure is a
Directed Acyclic Graph (DAG).
Let's assume in the above program, the Presenter team decided to produce a
new release. The team that will be affected by the release is the Main and
View. This is because they are the two next team when we follow our
dependency arrow backward. Thus when a new release is to be initiated by
the Presenter team, the three teams will have to come together to agree on
when to start using the new release.
However, when the Main team releases a new change, no team is affected
because the Main class has no team dependent on it. Hence, the impact of
the Main team on other teams is relatively small.
How A Cycle Affects the Component Dependency
Let's assume that in the program above, a new requirement is to be made in
the Entities class. This should be done in such a way that the new
requirements will also be used in the Authorizer . This action cannot be
carried out without using a dependency cycle.
The developer working with the Database class is aware that for the new
requirements to be released, it must be harmonious with the Entities class
and the Authorizer class. But the Authorizer in this program also depends
on the Interactor . This complication makes it very difficult for the
database to release new requirements without affecting all the other teams.
How then do we fix this problem?
Breaking The Cycle
There are two major ways to carry out this exercise
In this diagram, we note that two arrows are leaving the DatabaseAccess
class. Each of these arrows points away from the DatabaseAccess. This
means none of these two arrows are aware of the existence of the
DatabaseAccess. The second diagram below shows how a line is cut
through the database and the business rules.
In the diagram above, we will observe similar occurrences like the first
diagram. The database knows about the BusinessRule, but the
BusinessRule does not know about the Database. This can be interpreted as
the Database Interface exists inside the BusinessRule while the
DatabaseAccess class lives in the Database. The interpretation of this
validates the assertion that the BusinessRule only makes indirect use of the
Database. The direction of the line in the diagram implies that the Database
is not important to the BusinessRule, but the Database itself cannot exist or
function outside the BusinessRule.
The Database has the code that translates the code made by the
BusinessRule into the query language of the database. It is this code used
for translation purposes that knows about the BusinessRule.
From the explanation of our diagram, we now know that the business rule
can make use of any database. The changes or replacement made in the
database does not affect the fhd database. This implies that the decisions of
the database can be deferred. The database can use different
implementations like the Oracle, Couch, MySQL; this will not affect the
business rule.
The Input and Output Of A System
The most common definition of a system, especially by developers and
business managers, is the system is the same as the GUI. Based on this
misguided information, developers and business managers think the GUI
initiates the work of the system. However, there is a principle guiding the
input and output of a system. This principle is that IO is irrelevant. The
behavior of the IO is not the behavior of the system.
Let's take the video game as an example. In using the video game, what we
usually pay attention to are the mouse, the screen, the sound of the game,
and the interface. We ignore the most vital component of the video game,
the set of data structures and functions driving the interface. The system
does not need the interface for it to carry out its function; it can execute all
its functions without the game being displayed on the screen. This analogy
applies to higher systems. Like the database, the interface does not matter to
the business rule.
The Plugin Architecture
Software Development concentrates on how to create a software plugin that
enables scalable and maintainable software architecture. A plugin in which
the basic business rules are separated from those components that are
optional and can be implemented in different forms.
Let's assume in building our software, we decide to make the database and
user interface a plugin. This means we can plug in different kinds of user
interfaces like the Web-based, SOA based, and many more. For the
database, we can replace it with any other database like NoSQL, SQL or a
system-based database.
In the initial creation of our web-based, making a replacement will not be
difficult, but writing the plugins for the client-server User Interface might
be challenging. Some of the communication between the new user interface
and the business rule might have to be reworked.
In designing our plugins, we will select the module we want to depend on
each other from those we don't want to. For instance, we don't want the
business rule to be affected when changes are made in some parts of the
systems, and so we select and separate this group. Also, we don't want
changes in some parts to create changes in unrelated parts of the system. As
a result, we arrange our systems into plugins. This helps to create firewalls
that impede the propagation of changes. This will work in such a way that
when, for instance, the GUI is plugged into the business rule, changes in the
GUI will not create any change in the database.
This brings us to the assertion that boundaries in software architecture are
drawn where there is an axis of change. The component of elements on one
side of the boundaries changes at different times and for different reasons
from the component at the other side of the boundary. For instance,
business rule changes for different reasons and at different times, so there
should be a boundary between the business rule and the GUI. Similarly,
GUI, as a dependency element, changes for different reasons and at
different times, so there should be a boundary between it and the business
rule.
The explanation of the plugin above reinforced the Simple Dependencies
Rules.
Boundary Anatomy
The boundaries of a software system differs in form and size. Below is the
explanation of some sizes we have in the software boundary.
Boundary Crossing
To create a boundary-crossing, all the architect needs is to manage the
source code dependencies. Boundary crossing is a way of creating a
software boundary such that the functions on one side of the boundary calls
on those at the other side and pass along some data. Here, source code
determines the effectiveness of our boundary-crossing because changes in
the source code of a component will make other source codes to be
changed, recompiled, and then redeployed. Boundaries emphasized on
controlling and building firewalls against these changes.
The Dreaded Monolith
The commonest and easiest software architecture has no strict physical
appearance. This is simply an effective separation of data and function
within a single address space and a single processor. This process is referred
to as source code decoupling in our last chapter. Examining this process
from the deployment standpoint is what is referred to as Monolith - a single
executable file. The file can be a statistically linked C or C++ file or an
executable Java file.
It is noteworthy that the invisibility of our boundaries, while the
deployment process of our monolithic is ongoing, does not mean that the
boundaries are not present. This type of architecture usually depend on
some dynamic polymorphism to control their internal dependencies. This is
the reason why object-orient development is one of the most sought after
these days. Without the use of OO or some other forms of polymorphism,
the architect might fall back on the use of pointers to functions to achieve
their decoupling goals.
Calls from a low-level function to a high level is the easiest form of
boundary-crossing. In this kind of approach, both the compile-time
dependency and the run time dependency point to the direction towards the
higher level. Dynamic polymorphism is used when the high level wants to
invoke a service to the low level.
In monolithic, communication is always very easy and less expensive. In
the same vein, communication across source codes are usually very
interacting.
Deployment Components
Dynamically linked boundaries such as the Java jar file, Net DLL, Ruby
Gem are the simplest physical representation of architectural boundaries.
Deployment is carried out by delivering components in some binary or
some equivalent deployable forms. This process does not involve the use of
compilation. Deployment entails the gathering deployable units into some
convenient forms.
Communication across the deployment component is similar to that of the
monolithic. It is inexpensive and interactive.
Threads .
Both deployment component and monolith makes use of threads to carry
out their activities. Threads are a way to group or organize a schedule and
order of execution. It is neither an architectural boundary or a unit of
deployment.
Local Process
The local process is a very strong boundary in software architecture. It is
created by using the command line or other similar system call. The local
process runs in the same set of processors as the multicore but uses a
separate address space. This is because using the same address space is
prevented by the memory protection. It communicate with other component
using socket or other form of system communication like the mailbox.
Local processes can be a dynamically linked deployment component or a
monolithic linked component. In the monolithic deployment component,
various monolithic processes will have the same components compiles and
linked together with them, while in the dynamically linked deployment
component, the component may share the same dynamic linked component.
In the local process, the source code of the higher up process will not entail
the names, lookup key, or physical addresses of the lower up process.
Communication in the local process is carried out using the operating
system calls, Interprocess context switches, and data marshaling and
decoding. These applications are usually very easy to maintain and
inexpensive.
Services
Services are the strongest boundary in a software architect. Service process
starts from the command line, or other equivalent system Services does not
concern itself with the physical area of the software components. Two
services communicating together may not operate in the same physical
processor or multicore. However, communication across service boundaries
are usually very slow in comparison to function calls. The times for
turnabout can take up to tens of milliseconds to seconds.
Summary
This chapter explains in detail what boundary in software architect is. It
also explains all the different types of boundaries we have, ranging from the
simplest to the strongest. We also linked our explanation of some of the
boundaries to the decoupling processes we already explained in the
previous chapter. The next chapter focus on the three basic elements of a
software component.
Chapter Nine: Elements of Software Architecture
The most important elements of a software component are highlighted and
explained in this chapter. These elements include:
· Policy
· Level
· Business rules
· Entities
· Use case
· Response and request models
Policy
At the heart of any software system are policies describing what the system
is all about, the use, and functionality. However, these policies differ in size
and structure. Some are smaller and straight to the point; others are larger
and well-detailed. Whether small or large, software systems are statements
of policy.
The simplest system is usually written in this format:
the overall policy of the system is divided into smaller policies.
The policy guiding the business rule is separated from that of the database
or GUI.
All these divisions guide the user on how to transform input to output.
Among the important ways of building good software, software architect
should ensure he or she separates the smaller policies from the larger ones,
separates the policies that applied to one component from those that applied
to the other, and separates policies that might be subjected to change from
those that might not. Policies that change for the same reasons and at
different times should be separated from those that do not.
In designing good software architecture, the regrouped component is
formed into a directed acyclic graph. Policies at the same level are grouped
into a component using the nodes of the graph. The direct edges form the
dependencies between components. The direct edge is used to connect
components that are at different levels.
In every good software architecture, the direction of these dependencies
hinge upon the level of the component they are connected to. Most times,
the low-level dependencies are designed to rely upon the high-level
dependencies. These dependencies are source codes and compile-time
dependencies.
Level
The level is simply the distance between the input and output of a software
system. The extent of the level of a system is determined by the distance
between the policies guiding the low-level components and the high-level
component. The farther the policies of the low level from the high level, the
higher their level. The policies guiding the input and output of a system are
the lowest kind of policies.
Business Rules
In a simple definition, these are the rules that help determine the profits
made from the software system. They are the rules used to make or save the
business money irrespective of whether they are saved on the computer
system or saved manually. For instance, the additional charges given by a
bank on a loan is a business rule. How the bank chooses to save this money
does not matter.
Business rules are very critical to the software system; they usually
determine the productivity of the system. They are the rules guiding the
need for the software itself. It is this aspect that stakeholders and business
managers are concerned with. This is why they are sometimes referred to as
critical business rules. Critical business rules will exist whether or not there
is a system to automate them. However, they often require some data to
work with. Our bank loan example will require the interest rate data, loan
balance data, and payment schedule data to work with. These data are
referred to as Critical Business Data. These rules exist whether or not the
system is automated or manual. Both critical rule and critical data are
mutually dependent. In programming, the combination of these two is
referred to as Entities.
Entities
This is the object embedded in our computer system containing part of the
critical business rules operating on the critical business data. The Critical
business data is enclosed in the entities, or the entities are given free access
to these data. For instance, our Bank loan example will contain three-piece
of Critical Business rules: principle guiding the loan process, rate or amount
of money required, and period of the loan. Each of these three-piece rules
represents a class.
When we build a class like this in our system, we have successfully
separated the software that implements the critical business rule from the
others in the system we are building. The class we have created stands as a
representative of the business. It has no business with the user interface,
database, or third party framework. It works for the business irrespective of
how the system was presented or the data was stored. The entity is strictly
for business purpose and nothing more or less.
To create an entity, all you have to do is to bind Critical Business rule and
the Critical Business data together into a single and separate software
module.
Use Cases
There is some business rule that is not as pure as the Entities . This type of
business rule defined and determined the way an automated system is used
in other to save and make money for the business. This type of rule cannot
be used manually; they only work with an automated system. Using our
bank loan as an example, the bank officers might decide that for anyone to
be qualified for a loan, he or she must have a piece of valid contact
information and credit balance above 30,000 nairas. As a result, all this
information must be validated before the implementation of the loan. To
avoid compromising this standard, the bank specifies the condition on the
system. This example is a Use Case . It described the way the bank loan
data is used. The bank loan data here is a Critical Business data and also an
automated one.
A use case can, therefore, be defined as an automated system that specifies
requirements such as the input to be provided by the user, the processing
steps for the output to be returned to the user. A use case defines
application-specific business rules against the Critical Business Rules
within the Entities.
The use case involves the rules guiding when the Critical Business rules
should be initiated and how the rules are to be initiated. It does not have any
business with the user interface but indirectly specifies the data coming in
and out of the interface. The use case cannot determine where the
application is to be delivered from, whether on the web, the console, or on
the thick client. They do not describe the application to the user; rather, they
focus on the application-specific rules governing the interaction between
the user and the Entities. The use case does not describe the input and
output of the data.
The use case is an object with one or more functions used to implement the
application-specific rules. It entails the data elements needed to implement
the input data, the output data, and the appropriate entities with which these
data interact.
Entities are not aware of the use case that controls them. This assertion
echoes the Dependency Inversion rules for high-level components. Entities
are a very good example of the high-level component, while the use case is
an example of a low-level component. The low level is aware of the high-
level components, but the high level is not aware of the low-level
components or its influence upon it.
Reasons why Entities are High Level while Use Case is Low-Level
Entities are high level because they belong to the components that can be
used to call different applications; hence they are far away from the input
and output of the system. However, the use case is specific to a single
application. As a result, they are very close to the input and output of the
system. Entities do not depend on the use case, but use case depends on
entities.
Response and Request Models
The use case is very close to the output and input of a system. It expects the
input and uses this to produce the output. However, a good use case should
not have any connection with the way the data is communicated to the user
or to any other component of the software system. Our code within the use
class should have no business with the SQL or HTML.
The request data accepted by the use case for the input process and the
output are not dependent on anything. This absent of dependencies is
essential. If the request and response models are not independent, the use
case that works with them will be inadvertently bound to the component
any of these two depends on.
Summary
Of the three aspects explained in this chapter, the business rule is much
more important. It is the main reason for the existence of the software
system. They determine the extent of productivity of the system and
contains the code that saves or makes money for the business organization.
As a result, business rules should be well-detailed, clean, and comfortable.
The codes in the business rule should be the major concern of the
developer. The codes should have less plugin making the business rule the
most independent component in the system.
Chapter Ten: Theme and Purpose Of Software
Architecture
This chapter explains the theme and purpose of software architecture.
Topics in this chapter include:
· Screaming architecture
· Theme of a software architect
· Purpose of a software architect
· Testable architect
· Clean architect
· Dependency Rule
· Entities and Use case
· Interface Adapter
Screaming Architecture
Let assume we are presented with the document containing the design of a
one-family apartment. We will expect to see details such as:
The entrance to the building and a hallway guiding us to the first main
room: the living room
Just at a corner behind the living room, we will expect to see the dining
area. A set of chairs and a table will likely make this very obvious.
· Close to the dining area is an entrance leading to the kitchen.
· Next to all these details are rooms leading to each of the family
room.
No argument, all these details will scream one word: "Home."
Now the question is, what does our software design screams when we are
presented with the layout? Does an application entailing the source file in
the highest level component and a top-level directory structure scream an
accounting software or otherwise? To create a perfect architect, two things
are involved. The first is the theme of the software, and the second is the
purpose of the software.
The Theme Of The Software Architecture
Just as the architect of a house screamed a house when presented with the
design, so should our architect scream its use case. In the design of the
house, the use case is the details of the layouts that make the house. Without
being told what each room is, we can differentiate a dining room from a
kitchen and a kitchen from the living room. Similarly, a system should
scream about the use case of its applications.
Frameworks do not determine the software architecture. Hence architecture
is not about frameworks. The frameworks of architecture are only tools to
be used when designing the theme of the architect. They are not the theme
of the architect but used for creating the theme. When an architect is all
about the framework, it loses focus from the use case of the architect. A
good architect is expected to develop a strategy that prevents the framework
from taking over the architecture.
The Purpose of an Architecture
A good architecture revolves around the use case of the system and not the
frameworks, environments, or tools of the system. The use case describes
the structure of the architecture and, at the same time, explain the purpose
of the architect. A good software delays decision about the databases,
frameworks, environmental decisions and web server. Framework options
are to be left open and not taken as the priority of the architecture. A good
architecture delays decision hinging on the Tomcat or Hibernate, Rail or
Spring, MySQL and so on. A good architecture emphasis more on the use
case and makes it easy to change decisions that do not have any relationship
with the use case
Testable Architecture
When your architecture is designed in a way that it focuses on the use case
while keeping the frameworks at arm’s length, then you will be able to unit-
test the framework without affecting the frameworks of the system. To do
this, you won't need the webserver or database to run your test. The objects
of your entity should be plain, old objects that does not share any form of
dependencies on the database or framework or other components. Rather
your use case object should control and manage your entity. All of these
should be testable in situ.
Clean Architecture
Over the last few decades, we have witnessed different types of ideas on
how an architecture system should be designed. These ideas include:
BCE: Introduced by Ivar Jacobson, from his book Object-oriented Software
Engineering: A Use-Case Driven Approach
Hexagonal Architecture (commonly referred to as Ports and Adapters
developed by Alistair Cockburn, was taken on by Steve Freeman and Nat
Pryce in their book Growing Object-oriented Software with Tests
DCI from James Coplien and Trygve Reenskaug
All these ideas share a similar objective which is, the separation of concern.
These concerns are further divided into a layer. Every layer is made up of at
least one layer for business rule and another for the system and user
interface. Each of the architecture has similar details and characteristics.
These characteristics include:
Testable. The layer containing the business rule can be tested without using
the webserver, database, UI, or any other external element.
Framework independence. Frameworks are used as tools because the
architecture does not depend on the existence of library or feature-laden
software.
Database independent. Since the business rule are not bound to the
database, the database could be exchanged with others.
UI independent. This can be easily changed without affecting the use case
or changing the remaining elements of the system. A web UI can be
exchanged for the console without changing the business rule.
Independent of external agencies. Business rules have nothing to do with
the external agencies like the interface
The diagram below shows how a clean architecture should look like. All
components are integrated into a single actionable data.
The Dependency Rule
The concentric circles in the above diagram signify the various areas in the
software. The further in you go, the higher the software level becomes. The
inner circles represent the policies, while the outer is mechanisms. The
Dependency is the overall rules that do this software works.
The outermost part of this circle is where the framework, database, and all
other external components are located. As a general rule, codes are less
written in this area. The only codes that seem to exist here are the glue
codes used to communicate to the components in the inner circle.
The components in the inner circle are not expected to know anything about
the outer circle. In the same vein, anything declared in the outer circle must
not be mentioned by any code in the inner circle. These names include
variable, function, classes, and other named software components.
Additionally, when a data format is declared in the outer circle, it should not
be disclosed to the inner circles, especially if this format has to do with the
framework.
When designing your dependency rule, you may find out that you need
more than just four circles to carry out your mission. This is very acceptable
as there is no rule binding the number of circles required by a system.
However, the dependency rule always comes in. It states that all source
code dependencies should go inward. As you do, the level of policy and
abstraction increases. It covers a more high-level component until it
becomes the most highest and general.
Crossing Boundaries And How To Determine Which Data Cross
The Boundary
The lower right hand of the diagram is a perfect example of how data are
crossed in a circle. It shows how communications flow between the
controller, presenter, and the use case. The flow of control also started with
the controller and gradually moved to the use case, before summing it up at
the presenter. Also, in the flow of control, each source code point to the use
case.
This same method is used to cross all the boundaries in the architectures. To
create a boundary that conforms to the rule of dependency, we make use of
the dynamic polymorphism to create source code dependency that oppose
the flow of control.
From our explanation, we observe that all data that cross the boundary are
usually simple data structures. Simple data strut or simple data transfer
object can be used depending on the purpose of the architecture. The most
important thing to take note of is to ensure that your data structure does not
violate any of the data principles.
Entities and Use Case
Entities encompasses the enterprise-wide Critical Business rules and
Critical Business Data. Entity could be a set of functions or structure or an
object method. Whichever of the two does not matter as much as the
entities can be used by different.
The business object is entities that do not have an enterprise but are written
on a single application. The object entities encompass the high-level rules.
They are the rules that are not affected by any external changes.
As already explained in this chapter, the software comprising of the
application-specific business rules is the use case. These rules manage and
control the use case in the system, including implementing the use case.
The use case controls the flow of data going in and out of the entities and
also guide the entities into using the Critical Business rules to achieve the
use case goal or objectives.
Change in external elements like the database and interface does not affect
the use case. However, change in the operation of the system affects the use
case.
Interface Adapter
The interface adapter is a set of adapter in the software layer used to
convert data from the most convenient format for the uses case and entities
into the format most convenient for the external layers like the interface and
the database. The controller, presenters, and viewpoints all belong to the
interface adapter layers. The model is more like a database structure that
passes from the controller to the use case and then back from the use case to
the presenter and views.
In the interface layer, data can be converted from the form most convenient
for the use case to the form most convenient for other layers like the
database. No code from the interface adapter layer should know anything
about the database. If, for example, the database is a SQL, all the SQL
should be restricted to this layer or other layers contained in the interface.
Summary
This chapter has extensively explained what screaming architecture is and
what makes a clean architecture. The theme and purpose of a software was
also explained while paying attention to how boundaries are created and
crossed in an architect. Explanation on the interface and the database as an
example of low-level components are also given. We also examine the
interface adapter. In boundary-crossing, conforming to dependency rules
will help prevent errors from occurring and also help create software that is
not only testable but also has all the necessary benefits.
Chapter Eleven: Boundaries
In this chapter, we will cover the following:
· Partial boundaries
· Layers and boundaries
· Test boundaries
Partial Boundaries
Working with a full-fledged architectural design is very expensive and time-
consuming. Full-fledged architecture requires that the architect use the
input and output data structure, reciprocal polymorphic boundary interface,
and all the dependencies that are needed to separate the two sides into
independent compilable and deployable components.
As a result of the high cost of human resources and the time needed to run
this type of boundaries, architects will design their boundaries in such a
way that there is still an avenue to switched to the full-fledged boundaries if
need be. This type of boundary design is what is referred to as partial
boundaries.
Partial boundaries entail all the necessary independently compilable and
deployable components, but these components are kept in a single
component as against separating them. What differentiates a partial
boundary component from a full-fledged boundary component is that a
partial component has all of the details needed for the full-fledged
boundaries, but these details are kept in a single component. In a full-
fledged boundary, the components are separated and the administration of
multiple components is used. In partial boundary, there is no release
management burden or version number tracking.
Layers And Boundaries
It is much easier to think of a system as made up of these three components
The database
User Interface
Business Rule
How sufficient these three components depend on the software system the
architect is designing. Some system like a computer game might not require
more than these three components. Other larger system like a software for a
bank loan will require more than these three components.
Test Boundaries
Tests are part of a software system. They also form part of the component
of the system and participate in the function of the system. From an
architect’s point of view, all test is the same irrespective of size or shape. A
tiny test like TDD are as complete as a large test like the Cucumber,
FitNesse, SpecFlow, and so on.
From the explanation above, it means that the test is complete and well
detailed in themselves. By their nature, all test follows the dependency rule;
their dependencies are usually inward. Hence, like the database, the test is
part of the outermost component of the system circle. Nothing inside the
circle depends on the test.
Test are also independently deployable. This is why most times, they are
deployed in the test systems and not in the production system. It is the most
isolated component of a system; no part of the user case depends on them.
Their main function is to support the software development process and not
the operation process.
Testability
Developers usually make the mistake of regarding test as outside the system
component. This assumption is as a result of the fact that test is not often
deployed and are extremely isolated. When a test component is not well
integrated into the system, the system becomes too fragile, making it hard
to change. This kind of issue arose as a result of the lack of coupling
Test that is well coupled into the system changes along with the system. The
simplest change in a system can lead to hundreds of changes in the system.
When issues like this occurs, it is known as Fragile Test Problem. A good
example of this problem is the group of test that uses the GUI to verify
business rule. This type of test will first start in the login structure and
navigate to the page structure until they find the particular business rule to
be applied to. If this is the case, a slight change in the navigation structure
can cause a thousand of a break in the test.
This type of problem can make the system very rigid. This is often a result
of the developer avoiding creating any change to avoid the Fragile Test
Problem.
The best way to avoid this is to design a testability. The major rule guiding
this solution is to avoid dependence on volatile things. The User interface is
volatile; any test suite that uses this element will be fragile. Hence it is
advisable to create a design and test in which the business rule can be tested
without using the GUI.
How To Create Testability
The best way to do this is to create a special API for the test to use and
verify the business rules. The API should be very strong and effectively
designed in such a way that it can bypass the expensive resources such as
the database, avoid security constraint and then force the system into a
particular testable states. The API will be a superset of the interactor and
interface adapter used by the interface.
The API aim ate decoupling the test from the application. It decouples the
structure of the test from the structure of the application.
What Is Structural Decoupling?
This is the strongest form of test decoupling. The API created for the test
hides the structure of the application from the test. This action allows the
test not to be affected when there is a change in the other components
connected with it. The API used to carry out this action should be stored in
a separate independently deployable component.
Summary
This chapter covers all that is needed to know about the test. Tests are some
separate entities. They are part of the system and needed for the
effectiveness and stability of the system. When a test is not designed as part
of the system, it makes the system difficult to maintain. Also, the chapter
explains the difference between a full-fledged boundary and a partial
boundary. It explains the different layers we have in a boundary. In the next
chapter, we focus on the details of a system.
Chapter Twelve: Significant and Non-Significant
Architectural Components
The focus of this chapter is on components that are significant to the
functions of the software architecture and those that are not. The three
software components that will be explicated include presenter, humbling
object, and service. Each component will explore based on its contribution
to the function or use case of the software architecture. After this
explanation, developers will be guided on how to include these components
in their system. Below are the outlines that will be covered in this chapter:
· What is a presenter?
· Explanation of the humble object pattern
· Presenter and View
· Great and small service in software architecture
· Importance of service to software architecture
· Avoiding cross-cutting concerns.
What Is A Presenter in Software Architecture?
In software development, a presenter is a form of humble object pattern. It
helps the architect in protecting and recognizing architectural boundaries.
The question that arises from this definition is, what is the humble object
pattern? How is this pattern created and recognized in software
development?
What Is An Humble Object Pattern?
The humble object pattern is a design originally created to help developers
when testing their units. The pattern makes it easy for developers to
separate units that are too difficult to test from those with easy testability.
To use this pattern, the developer will divide the units of the system into
two parts. The humble module is the unit that contains the hard-to-test
elements, while the second part contains all the testable behavior that was
stripped out of the humble objects.
A good example of units that are difficult to test is the GUI. This is because
it cannot display a written test in the unit on the screen. The developer
cannot see if the test written in the GUI is correct or not. But most of the
behavior of the GUI is easy to test. Using the humble pattern for the GUI,
the developer will separate the GUI behavior that is easy to test from those
that are difficult to test. These two parts will be referred to as Presenter and
View.
Presenter and View
On the one hand, the humble object unit that contains the elements that are
hard to test is referred to as the View . The objects in this unit are very
simple. The code does not process any data in the GUI; it only moves data
to the GUI. On the other hand, the units that contain the testable objects are
the presenter . The presenter receives data from the application, format the
data, and then sends it to the View who moves it to the screen. Take, for
instance, our application wants to display shopping data on the screen. A
shopping object will first be sent to the presenter , and the presenter will
format this object into the required spring and then place it in the View
Model, where the View unit can easily locate it. The View unit will move the
object to the screen.
Each button in the View model is given a name by the Presenter . This is to
make it easy for the View unit to locate the object. If any of the buttons
grayed out, the presenter will set a boolean flag in the View model. Since,
after formatting the object, the Presenter converts it to converts object string
before sending it to the View, what is usually sent by the Presenter to the
View mode is either a string or a boolean flag. After this, all that is required
of the View is to send any of these two to the screen. Hence, the View is
humble.
The humble pattern is a very good example of Testability. This attribute has
long been identified as one of the attributes of good architecture. This is
because what defines a software boundary is the separation of units into
testable and non-testable parts.
For every good software architecture, at each architectural boundary, the
humble object should be found. Since communication at every boundary
level usually involves some kind of simple data structure divided into a
hard component and an easy one, using a humble pattern at these levels will
increase the testable ability of the entire system.
Great and Small Services Of A Software Architecture
Micro-service software and Service-oriented architecture are among the
major software architecture we have today. The major reasons for their
popularity are:
Services now support the independence of deployment and development
Services are strongly decoupled from each other.
The widespread belief that services, by their nature, are systems is partially
true and partially untrue. To start with, software architecture is often
defined by their boundaries, and how these boundaries are effectively used
to separate high-level policy from low-level details, and also how the
boundaries are designed to adhere to the Dependency rule. Services that
only separate the behavior of the application are more expensive than
services that separate just the function calls. As expensive as they are, they
are not necessarily important to software architecture. This type of service
is what we will classify as small services.
This is not to say that all the services in a software architecture must be of
architectural significance. Services that mainly separate functionality across
processes and platforms are also very important, whether they adhere to the
rule of Dependency or not. The main point to note is that services
themselves are not very crucial to software architecture.
A simple way to illustrate this is to use the organization of function
approach. In the organization of function, the architecture of a monolithic or
component-based system is determined by the function calls that adhere to
the dependency principle when crossing any architectural boundaries. Other
functions that merely separate the behavior of the software from the other
are not of architectural significance.
Like function calls, some services are of architectural significant while
others are not. In fact, some services are function calls themselves.
Importance of Service to Software Architecture
Decoupling Fallacy
The most noticeable benefit of separating components into service is
decoupling. Each service unit is processed independently and can be run
even in different processors. This type of service does not have any direct
access to each other's variables.
It is important to note that services can only be separated at the level of an
individual variable. But, they can still be coupled at the level of resources or
at the data level. This statement restrains the decoupling benefit to the level
of variables alone.
The Fallacy of Independent Deployment and Development
This benefits hinged on the fact that service can be owned and managed by
a dedicated team. This means, the team writes, maintains and operates the
service of the software architecture. This kind of independence is assumed
to be scalable. This is because a large scale system can be generated and
created from hundreds and thousands of services that are independently
deployable and developable.
The Problem with this Fallacy
Based on the explanation of the above two fallacies, in building the service
of our system, we will want our system to be scalable. To do this, we will
build it from different deployable and developable services. After this, we
then divide our team of software development into smaller teams, each
responsible for maintaining, operating, and writing the service.
Suppose that after creating the service and mapping out areas of
concentration to each group in our team, the service is running fine, and
each group is owning up to the task given to them. But after a year, a
meeting is called, and a new service is to be created. What immediately
comes to mind is how many components of the previous service will be
altered. The simple fact is everything will have to be changed and recreated.
This problem is inevitable in software architecture. Every software, whether
it is service-orient or not, must face this problem. How then do we solve the
problem?
Object to The Rescue
How do we solve the problem explained above now that we are using a
component-based architecture? Quickly, we go back to our SOLID
principle. Part of the principles will prompt us to create a new set of the
class whose function can be polymorphically extended to the handle other
features. Hence, the feature of the new service will then be incorporated
into the new set of the class created. These two components will adhere to
the rule of Dependency. The two components are coupled to the abstract
based class in the original class using a pattern such as Template method
or Strategy. Some of the factories under the management of the UI are
used to create a class that implements the method.
When the new feature is implemented, the feature of the old service
changes, but aside from this change, every other remains a theme. The new
system is decoupled and independently deployable and developable.
From the above explanation, it becomes obvious that services do not have
to be small monoliths. In fact, a service can be created using the SOLID
principle. With this principle, we can add a component class to our system
such that part of the component class makes it possible to add more services
to our system without altering the components of the former service. With
this technique, creating a new system does not necessarily involve
redeploying our services. Rather it is a matter of adding new files to our
former service. This approach also conform to the Open-Closed Principle
Avoiding Cross-Cutting Concerns
We already started in our previous chapter that architectural boundaries cut
through the component that makes our service group and divides them into
smaller classes. To handle cross-cutting concerns that state that all
significant system services must be designed with an internal component
architecture that adheres to the Dependency principle, our services will be
designed in such a way that it does not define the architectural boundary of
the system. This duty is transferred to the components subsumed within the
service.
Summary
In this chapter, we have explored another major aspect of software
development. This aspect includes presenter and humble object and the
function of service in software development. Every clean architecture will
have to conform to the steps guiding the humble object patterning, and the
steps guiding the presenter and view and how service is created in software
development. While presenter and humble-object patterns are core to the
software architecture, services are not of architectural significance.
Chapter Thirteen: Clean Embedded Software
Architecture
This chapter provides a detail explanation of clean-embedded software;
there is more to creating software to just focusing on the working ability. In
this chapter, we will be examining the topics highlighted below.
· Clean embedded software architecture
· App-titude test
· Target-hardware bottleneck
· What is the OS?
· Interface programming and substitutability
· Dry conditional compilation derivative
Clean Embedded Software Architecture
In one of the articles by Doug Schmidt, he said something very remarkable,
he said:
Although the software does not wear out, firmware, and hardware obsolete,
thereby requiring the software modification.
From Doug's statement, it can be inferred that software has a longer life
span than firmware or hardware. Hardware, as we know, is continually
evolving and improved on. Based on this fact, it is not uncommon for
embedded software that is dependent on hardware to be denied a longer life
span due to its dependence.
As against popular belief, the firmware does not mean the code is stored
inside the ROM. It is not called firmware because of the location where it is
stored but because of its dependence and the difficulties encountered when
one tries to change it as its hardware evolves.
For non-embedded software developers, the firmware is incorporated into
their system whenever SQL is planted in a code or when dependencies are
prepared across all the code of the system. Android application developers
write firmware when the business login is not separated from the Android
API. The best way to avoid creating embedded firmware is to quit writing
too many firmware in a program and give your code a chance to live. We
will be explaining in detail how embedded software is kept clean to give the
software a long life span.
Ap-titude Test
This test shows that the major reason embedded software becomes
firmware is that software developers usually focus on getting their codes to
work instead of focusing more on the structure of the software in other to
increase the life span of the software. Kent Bent summarizes this test in
three statements.
First, make it work
Then make it right
Then make it fast
Today, most of the software developers have focused more on the first and
third options with little or no attention paid to the second option. The "make
it fast" option is usually done by adding micro-optimizations to the program
at every interval. Getting the app to work is what is referred to as an ap-
titude test. There is much more important to software architecture than
getting the program to work. When an application is designed with only the
working purpose in mind, it is not a clean embedded software architecture.
The Target-Hardware Bottleneck
There are a lot of things developers building embedded software have to
take note of that might not be applicable to non-embedded developers.
These things include real-time constraints and deadlines, limited space,
limited IO, censors and connections, unconventional User Interface, and
many more. Usually, the hardware is developed alongside the software. As
a software developer, in trying to build codes for this kind of software,
difficulties arise. It's the hardware that has its own defect making the
software development process slower and time-consuming, or the codes are
too difficult to run.
Embedded software and embedded software engineers are indeed very
special. But they are not too special to ignore the principles in this book.
When embedded software is developed without strict adherence to the
principles explained in this book, the target-hardware bottleneck problem
might arise. The target-hardware bottleneck problem is a situation where
codes only run in the target and not around all the components of the
software. When this occurs, the system becomes very slow.
Controlling The Target-Hardware Bottleneck
Applying The Layers Control
Layers can come in different ways. In our explanation, we will be using
three layers: hardware, firmware, and software. Our hardware will be
stationed at the bottom end of the layer. This is in accordance with Doug's
rule and Moore's Law. The hardware changes as technology evolve. Part of
it will be obsolete, and new ones will come with low cost and more power.
To control our system, we fix the hardware at the down part so that
whenever changes occur, it will harm the entire system.
To pass the app-titude test, developers often keep the other layers from
knowing about the software. This method does not always help the system.
In fact, nothing keeps the other layers from knowing about the hardware.
Knowledge does not affect the system in any way. If layers are not carefully
fixed, the system might become hard to fix, especially when users request a
change.
The other two layers comprise the software and the firmware. Be careful of
the way you fix the two. When the software and firmware are not well
separated and intertwined in a pattern, the code portraying this anti-pattern
will resist changes. Changing any component of the system might lead to a
disaster. For any small changes, the developer will need to carry out a full
regression test
The Hardware
In our program, notice that the line separating the hardware from other
layers is more clearly defined than the lines between the software and the
firmware. As an embedded developer, one of your major tasks is to firm up
the line, cutting across firmware and software. The boundary between these
two will create a hardware abstraction layer (HAL). This method has long
been in PC way back in the days of Windows.
It is noteworthy that the HAL only exist for the software sitting on its top.
Hence the API of the HAL should be integrated with that of the software.
For instance, the firmware can be used to keep arrays of bytes and bytes
into flash memory. In contract, the application needs to store and read
name/value pairs to some persistence mechanism. That the name/value pair
is stored in the Flash memory, core memory, spinning disk or cloud should
not be the concern of the software, rather the implementation of the flash is
a detail that should not be revealed to the software. The HAL provides a
service like the flash implementation; it does not reveal to the software how
the service is provided. HAL can be used to express the services needed by
the application. A Layer can also contain more layers in it, but this should
be like a repeating fraction pattern than a set of predefined layers.
In addition to the explanation so far, it is noteworthy that of the qualities of
clean embedded software is, it has the ability to be tested off the target-
hardware. Therefore, a good and well-defined HAL will provide the
necessary substitution point that facilitates off-target testing.
The Processor
It is important to note that whenever an embedded application uses a
specialized toolchain, it will need to provide a header file to <1> help you
</i>. In C programming language, these compilers often take liberties; new
keywords are added to the program to access the processor features. At the
end of the application, the program will be similar to a C, but it is no longer
a C.
Most times, in other, to give direct access to the processor registers, clock
timer, 10 bytes, interrupt controllers, IO port, and other processor functions,
the vendor-supplier C compiler provides what looks like global variables.
Although it is very helpful to have quick access to these processors
function, once these codes are given, the program is no longer a C. It will
neither compile with a different compiler for the same processor or for a
different processor. Therefore, in other to use this in a way that it does not
create another problem in the future, as the developer, you will have to
reduce the files that should know about the C extension.
In some cases, all the software can be designed in such a way that they can
be processed independent, but not all the firmware can be. A clean
embedded software confines direct access register processors to the
firmware. All the application that knows about this register is confined to
the firmware and bound to become silicon. In this kind of situation, when
you want to make your code work before you build stable hardware,
integrating your code together will create some challenges. It will also be
challenging to move an embedded application to a new processor.
However, you can use your firmware to isolate low-level functions using
some form of processor known as processor abstraction layer (PAL). The
firmware above the PAL can be tested off target. This made it a little less
firm.
The Operating System
From our explanation of the use of HAL, it can be inferred that in other to
create a clean embedded software, the use of HAL is necessary, but this is
not sufficient on its own. HAL does not work for all the programs. For
instance, using HAL is always enough in bare-metal embedded programs;
in this type of program, HAL is used to restrain our codes from getting too
close or integrated to the operating system. However, when dealing with an
embedded system that uses Real-time operating system(RTOS) or even
some embedded version of Window or Linus HAL is inadequate
Therefore, in other to give our embedded software a long lifespan, our
operating system should be treated as a detail. Details in software
development is a broad topic of its own. This is what the third and last
section of this book is all about. The operating system should be used as
detail and protected against the OS dependencies. When this is done, the
software will be able to access the service of the operating system through
the OS.
What Is the Os?
The OS is a boundary layer used to separate the firmware from the
software. The OS cannot be directly used; if used directly, it creates a
problem. This problem can arise from the situation as inevitable as the
RTOS of your program sold to another company making the quality go up
or down; the need of the user might even change. Problems will arise from
this kind of when your RTOS does not have the requirements for the new
needs. To rectify this, you will need to create new technical codes. This type
of is not simple synthetic changes because of the direct use of OS. But they
will be adapted semantically to the new OS's different capabilities.
Every clean, embedded architecture will separate the software from the
operating system. This will be done by using a layer called the operating
system abstraction layer (OSAL). Most times, implementing this layer in a
program is as easy as trying to change the name of a function; at other
times, it will take wrapping several functions together.
Moving software from one RTOL to another is a very difficult task. Instead
of the direct use of the OS, you can write a large new OSAL that is very
compatible with the old OSAL. The new OSAL layer becomes the place
where most of the duplication that comes from using an OS is isolated. This
does not need a big overhead. If your OSAL is already defined, you can
encourage your application to have a common structure. To make the
valuable application code to be successfully tested off-target and off-OS,
the OSAL can be used to provide test points.
Every clean embedded software architecture can be tested off the target
operating system. A well-defined OSAL will be capable of providing the
seam or set of substitution points that are necessary for off-target testing
facilitation.
Interface Programming And Substitutability
When creating a clean-embedded software, aside from the OS and OSAL
added to the program, your development process must adhere strictly to all
the principles explained in this book. It is with these principles that
separation of concerns, interface, and components can be accurately done.
The principles will also guild you into achieving valid programming to
interface and substitutability.
Programming to interface gives rise to the idea of layered architecture. In
this approach, when a module communicates with another module through
an interface, service providers can be substituted. One of the most important
rules of thumb is to use the header file as interface definitions. When doing
this, all care should be taken about what goes into the header. Header file
contents should be limited to function declaration, constant and struct
names, and so on.
Information like the database structure, typedefs, and the constant that are
used by the implementation should not be stored in the interface header.
This is not just a matter of clustering the interface header, but this type of
cluster can lead to unwanted dependencies. Limit the details of the
implementation. The reason for this is simple; details are susceptible to
change. Hence the fewer the place where codes know the details, the fewer
the places that will have to be modified when implementation change
occurs.
Clean embedded software can be tested within the layers because modules
always interact through the interface. Each interface provides the
substitution points or seams that are off-target testing.
Dry Conditional Compilation Directives
One of the uses of substitutability that is not often taken cognizance of is
related to how embedded C++ and C handle different target or operating
systems. As a result, the tendency to use conditional compilation to turn
segments of codes off or usually occurs. The incessant repetition of codes
goes against the principle of DRY (Don't Repeat Yourself). Conditional
compilation is used to recognize the types of target-hardware that are often
repeated in an embedded system.
Summary
From the explanation of embedded software and the various rules guiding
its use and creation, we can say that developers who are interested in
creating clean embedded software have a lot to learn. This learning cut
across both software architecture and development. Allowing all your codes
to become firmware is not good for the lifespan of the software. At the
same time, allowing testability only in the target-hardware can impede the
lifespan of the software. A clean embedded software encourages off-target
testing in all areas of the software.
Chapter Fourteen : Details
In the introduction of this book, it was mentioned that the books are
sectioned into three. The first and second has been extensively explained in
the thirteen chapters we have explored. The remaining last section will be
covered in this chapter and the next. In this chapter, we will cover the
following outlines:
· The database is a detail
· The web is a detail
· The framework is a detail
Details are components of the software that are of no architectural
significance. This implies that they play no important role in the use case of
the software. This type of is located at the outer surface of the program
circle already explained. We also stated in our explanation of the circle that
elements on the outer part of the circle depend on those on the inner circle.
This is in accordance with the Dependency Rule.
The Database is a Detail
Virtually in all the topics so far, we have made mention of this particular
element of software architecture. But in this section, this class will be
explained in detail.
To start with, the database is a non-entity. This implies that the database is a
detail that is not crucial to the architectural level. Often times, programmers
and developers the database as the data model. The two concepts are not the
same; they represent different things in software architecture. The data
model encompasses the structure you give to the data contained inside your
application. This feature is very key to the software. Database, however, is a
piece of software. It is the item that provides access to the data. In the
domain of software architecture, this item is not significant because it is a
low-level detail - a mechanism. An excellent architect will not allow this
item to stand as an impediment to the neatness or wholeness of the
software.
Relational Databases
In the year 1970, Edgar Codd was the first programmer to define the
principle of relational database. By 1980, the model grew to become one of
the prominent and most widely used forms of data storage. The reason for
this prominence is because of the simplicity and usefulness of the principle.
It is one of the most important methods of data storage. However, no matter
its importance, database remains a low-level detail.
Relational databases use of relational table. In the table, data are arranged in
rows. This approach is not only simple but also very neat to use.
Notwithstanding, the tables are not known by the use case, and the use case
does not need it to perform its action. Hence, the knowledge of this tabular
form of utility is only revealed to the low-level functions available outside
the circle of our program.
Many developers make the mistake of allowing the database row to go
around the system as objects. When this happens, it coupled the business
rule, use case and even the User Interface to the relational structure of the
data. This creates an architectural error that might be very difficult to
correct.
What Makes a Database System so Prevalent?
When we look at software today, it is dominated by database systems like
MySQL, Oracle, and so on. Why is this the case? The reason for this is
simply the one word: DISK
For more than five decades now, the mainstay of data storage had been the
rotating magnetic disk. Most programmers are not even aware of other
forms of storage. The size of the disk keeps evolving as technology
progresses. It has risen from a huge stack of the massive platter to a thin,
simple circle. Its capacity has grown from as low as 20 megabytes to a
terabyte or more. However, all throughout its progress, there has been one
consistent problem that has to remain indomitable. This problem is that
Disk is very slow.
Data are stored in a disk in a circular track. This track is broken into smaller
sections of bytes. Each disk platter can have about hundreds of this small
track. When you want the read the data in the disk, you have to go to the
main track, wait for the disk to rotate to the proper section, read all the 4K
of that section into RAM, then index the RAM buffer to get to the track you
want. This process is usually time-consuming. It takes milliseconds
When we describe the process it takes to load a track in a disk as time-
consuming and describe the time it takes as the millisecond; it feels like this
is no time compared to all the other components in the system. However,
milliseconds are a lot more than the cycle of time it takes some processors
in the system. When data is not stored in a disk, it could be assessed in
nanoseconds instead of milliseconds.
To limit the time it takes for the disk to load, you will need a lot of things
like indexes, optimized query scheme, caches, and some kind of regular
means of representing the data so that the listed items will be able to know
what they are working with. You will also need a data access and
management system. The system is further divided into two types: file
system and relational database management system (RDBMS).
The file system is documented based, while the database system is content-
based. The file system provides a very easy way to store documents. When
you need to retrieve the name of the document, the file system works very
smoothly. However, they are very slow with content and might not even
show the content requested.
Database system works very well with the content. They are good at
searching out multiple with just the names. However, they don't work well
with opaque and vague documents
These two systems make looking for data on disk very easy. Each has its
own scheme for indexing and arranging data. When using any of the two,
data are quickly brought to its related RAM without much delay.
What Happens when the Disk is No Longer in Vogue?
Today, RAM is gradually taking over, and the need to store data in a disk is
gradually dwindling. With this in mind, what happens when disk is no
longer available and you have all your data stored in a RAM? How will you
organize the data?
The best way to go about this is to organize your data into list trees, queues,
or any other data structure and access it using either the pointer or
reference. In fact, when you think through the process of storing and
retrieving data in a disk, you will notice that even though your data are
stored in the database or file system, when you load a file in the RAM, you
will have to read and recognize it into any of the data structure of your
choice. Data is not read in the tabular rows.
Details
From the above explanation, there is no doubt that the database is indeed a
detail. Its main function is to help us move our data back and front the
surface of the disk and RAM. The database is no more than a place where
we store data.
Therefore, from the architectural point of view, we can say that the data
does not play any significant role when it is on the surface of the rotating
disk. In fact, during this period, we don't usually acknowledge the presence
of the disk.
From our explanation so far, it can be summarized that the organizational
structure of the data model is significant to software architecture. However,
the system that rotates the data on the surface of the disk is not significant.
Therefore, the data is significant, but the database is only a detail and not
significant. The relational system works more with the data and not the
database.
Web And Framework Details
The Web
Taking a step back into the 1960s in the wake of the webserver. When we
try to compare the web in those days to what we have today, we will see a
big difference that will confirm to us that the web has indeed changed. But
how true is this assumption? Did the web really change?
The web is a series of oscillation that moves back and forth between putting
all the computer power inside a central server or putting all the computer
power out at the terminal. This oscillation did not start with the web. Before
the advent of the web, this kind of oscillation exists in the client-server
architecture, central minicomputer, and so on.
Now a good look at the overall scope of the history of IT, we will come to
one realization. The web didn't change anything at all. The oscillation of the
web is central that should be separated from our business rules.
This upshot is very simple since the GUI is a detail, the web is a GUI, and it
is a detail. As a web developer, details such as the web, database, and GUI
should be separated from boundaries away from the functional components
of the system.
A Framework is a Detail
This is another interesting component in a software system. The first
important thing to note about the framework is that the framework is not
architectures. Frameworks authors are not software developers. Framework
authors write frameworks to solve a particular problem. For instance, when
you buy a framework, you read through it to know how to use it in your
software. In the documentation, the framework author explains how to
incorporate the framework with your system and urge you to couple the
framework with your system.
From the point of view of the framework author, coupling the framework
with your system is not a risk because he has control over the system.
Obeying this instruction can result in any of the following risks:
Frameworks do not usually follow the Dependency rule hence their
architecture is not always clean
At the early stage of your application, the framework might be very helpful,
but this stops as soon as your application progress
You may get stuck once the framework evolves in a direction you are not
interested in
You may wish to switch to a better framework but find this almost
impossible
Solutions
The best solution to this not to couple your program with the framework.
Once you get the framework to work, treat it as a detail and not part of the
inner cycle of the architecture. Let it remain on the outer surface.
Do not allow the framework into your vital codes. Instead of doing this,
follow the dependency rule and integrate it into one of the plugins for your
vital codes
Although, there are frameworks that require that you couple your program
with it. For instance, when you are using C++, you will have to merge STL
with your program. If it's Java, you will couple the standard library. This is
expected and normal. However, it is important to note that when you merge
a framework with your application, you are stuck with the framework for
the period you will use that application.
Summary
From our explanation so far, it becomes obvious that details are not as
crucial to the software architecture as we used to think.
Chapter Fifteen: Implementation and Case Study
All the aspect we have treated in the fourteen chapters of this book, if
followed accordingly, it will help us to create a superb software designed
with excellent boundaries, clearly defined responsibilities, and well-
managed dependencies. The principles explicated in this book are
guidelines for building very clean software. However, there are some things
to take note of in the implementation of the software. If this is not well
followed, it might make all our efforts come to waste.
To start with, let's assume we want to build an application for an online
book store. Among the requirements of the application is that the customers
can see the list of their orders. This kind of application seems like
something we can do on Java programming language alone, but it can
actually be done with another programming. The first step to designing this
application is to organize our codes and design. Below are the steps to
organize our application into layers. The method explained below is for the
four different patterns we can use to separate our layers.
Package by Layer
This type of layer is achieved by using the traditional horizontal layered
architecture. Here are codes are separated based on the functions they
perform in the system. This system of separation is called package by layer.
For our application, we have three layers in all. Our three layers are split
into a layer for business logic, a layer for code, and a layer for persistence.
What we have done here is to split our codes into layered grouped
according to the function they perform in the application.
Package by Feature
This type of separation or packaging can also be adopted for our codes. In
this approach, implementation is separated based on their classes using
vertical boundaries for related features. The class is then placed in a single
Java package. The package is given a name to reflect the concepts in it. The
top side organization of this type of code reflects something about the
business domain. Thus, the code class has more to do with a domain other
than with repositories or web. As a result, it is very easy to find all the
codes that need modifying when need be. This is because they are all in a
single Java package and not distributed around the program. Because of the
simplicity of this type of separation, programmers often find it difficult to
use horizontal layering and switch to vertical layering.
Ports And Adapters
Approaches like the hexagonal architecture, ports and adapters, controllers,
boundaries, and entities all aim to create an architecture where the business
domain is separated from details like the database, framework, and GUI.
This type of separation is in relation to the circle explained in previous
chapters of this book. Outside the circle, we have classes like the UI,
database, and framework, while all the domain concepts are stored inside
the circle. The rule guiding this type of separation is, the outside depends on
the inside and not the other way round. In our circle, we will separate the
two classes as domain and infrastructure. This is shown in the diagram
below:
Package By Component
Now let's assume that in our program, we used the layered package. This
means, all the dependencies downward while the next layer depends on the
next adjacent layer. This will help us achieve a clean acyclic dependencies
graph. However, the problem with using this layer is that we can cheat by
adding some dependencies, and our graph will still meet the acyclic
dependencies.
Let's assume that someone new was hired to join the team. Now the new
person was given an order -related list to implement. The newcomer will
want to impress the team by making the use of case work as timely as he
can. When the newcomer starts his work, he noticed a class called
ordercontrol and guessed that must be the place the code he needs for the
implementation is stored. The newcomer also noticed another class and
injected his implementation into the other controller class. After his work,
the webpage is working, but the UML is faulty.
The dependency arrow is still downward, but some changes have been
created in the program. This type of program is known as a relaxed layer
architecture.
The best way to avoid this kind of situation is a guideline. This should be in
the form of an architectural principle that says, web controller should never
access repositories directly. What is left with this rule is enforcement.
The above situation brings us to package by component. This is simply a
hybrid approach to what we have been treating so far. The goal of this
bundling is to place all responsibilities related to a single coarse-grained
component into a single Java package. Package by components keeps the
users interface separately the same way ports and adapter treat the web as
just a delivery mechanism. This approach integrates the business logic and
persistence code into a single component. Uncle Bobs defines a component
as a unit of deployment. They are the smallest part of a system that can be
deployed as an entity. Component in Java programming is known as jar
files. In this book, we will define a component as a related functionality
grouped behind a neatly designed interface residing inside an architectural
environment like the application.
Among the importance of package by component is, when writing a code
that has to do with order. You can easily go to the category of
ordercomponent . This will direct you to a component where there is a
separation of concern. This means that the business logic will be separate
from the business data.
Problem with The Implementation Details
On the surface, these four separation techniques, all like different
approaches that can be used for different types of applications. However,
when any of these packages is wrongly used, it will affect the software. A
very popular modifier that programmers have adopted without a thought
about its effect on the program is the public access modifiers. This modifier
seems ingrained in the muscle memory of programmers. This type of
modifier is very common in codes sample for tutorial, books and open-
source framework on GitHub. This modifier is popularly used, irrespective
of the package adopted in the program, whether horizontal or vertical, port
or adapter.
The disadvantage of this is, by making all your program public, you are not
taking very good advantage of all the encapsulation facilities made
available by your programming language. This brings us to the question,
organization, or encapsulation, which is better?
When you make all your program public, it means one thing, the packages
are organizational and cannot be used for encapsulation. By organization, it
means the program is groupings like a folder. Therefore, the use of
packages can be ignored since public program can be used from anywhere
in the code. Using a package in such a program does not provide any real
value. When the packages are ignored, because they don't really provide
any encapsulation assistance, the program you are creating might not be
problem-free.
When all your Java type program is made public, what we have in such type
of program are four ways to describe a horizontal program. But in doing
this, the programmer has to be very careful. The access modifier in the Java
program is not very neat. The fewer the public type we have in a program,
the smaller its dependencies potential.
If the program you are building is a monolithic program, where all the
codes reside in a single source code tree, it is advisable to lean on your
compiler to enforce your architectural principle, instead of relying on self-
discipline and post-compilation tooling.
From the explanation so far, it is advisable that when building a program,
pay attention to the implementation. If this is not done correctly, it could
destroy the program in a flash. Ensure you pay attention to how to map your
desired design on the code structure, how the code will be organized, and
which decoupling method to apply. Leave an option where it is needed and
take consideration of the size of your team and their level of skill.
Case Study
Here we are going to be applying all we have learned so far in this book.
The program we will be using is software for selling videos. The basic
information about the program is that we have a couple of videos we want
to sell, and we want to sell these to both individuals and businesses on the
web. The price range between streaming the application and downloading it
will be stated in the application. An individual can act as both the viewer
and the purchaser.
The first step towards creating our program is to identify the architecture of
the system. Hence we recognized the use case and the actors.
Use Case