left-icon

Object-Oriented Programming in C# Succinctly®
by Sander Rossel

Previous
Chapter

of
A
A
A

CHAPTER 6

Architecture

Architecture


So far you’ve seen a lot of code on small scale. We’ve seen how to design a single class, how to have multiple classes communicate with each other, and how to abstract away certain components (like a database). Chances are you need to write more than a few classes though. When looking at the high level structures of a software system, we speak about the architecture of that system. In this chapter I’m going to discuss some technical challenges and methods that you may encounter when building an application.

I want to emphasize what I will not discuss as well. I will not discuss the job of a software architect, which is talking to the stakeholders, deciding on development processes (Agile or Waterfall), documentation, budget, and other external factors that you’ll have to deal with when building an actual application.

The architecture of a system is the part that is not easily changed. Opinions on this vary, but let’s consider a database. Many people would point at the database, call it the heart of any system, and tell you it’s pretty hard to change later on in the development process. We now know, however, that when we abstract away the database behind a Data Mapper and a Repository (on which the software depends through abstractions) changing databases is “simply” a matter of switching our Data Mapper, and possibly Repository. Of course it’s still a lot of work, and everything well have to be tested thoroughly, but we shouldn’t have to change our front-end, for example.
So what is hard to change, then? Suppose half-way into the project you decide to change the interface of your Data Mapper or your Repository. That could be pretty problematic. Or maybe you decide to throw in an MVC and/or MVVM architecture. That will need a good rewriting of many components.

Don’t Repeat Yourself (DRY)

One important principle we have not yet discussed is the DRY Principle, or Don’t Repeat Yourself. Why do we go through all the trouble of putting everything behind abstractions, thinking about architecture, and reuse anyway? DRY is the answer. Prevent writing the same code twice. The next example will illustrate why.

Suppose you have a Person class which has save logic. We broke low coupling and high cohesion, but we can save our Person.

Code Listing 101: Person with Save Logic

public class Person

{

    // ...

    public void Save()

    {

        // Dataase logic for Person here.

    }

}

And now we need a SalesOrder. Because we don’t have a single class that can save domain objects, we’ll need to write the save functionality again, but this time for our SalesOrder. No problem, we’ll just copy/paste it from Person!

Code Listing 102: SalesOrder Class with a Bug

public class SalesOrder

{

    // ...

    public void Save()

    {

        // Dataase logic for Person here.

    }

}

It’s a no-brainer, but the programmer copy/pasted the Save method from Person and now the SalesOrder literally reads “Database logic for Person here.” I have seen this happen many times, even in production code!

There’s another problem though. The method doesn’t read “Database”, but “Dataase”. It seems we have another bug. Let’s fix the SalesOrder class.

Code Listing 103: Fixed SalesOrder

public class SalesOrder

{

    // ...

    public void Save()

    {

        // Database logic for SalesOrder here.

    }

}

Neat, our SalesOrder works now! But our Person class has the same bug. We must now remember to fix this or it will keep the same bug we just fixed in SalesOrder.

If we had one Save method that worked on both SalesOrder and Person we would not have the first bug (SalesOrder saving a Person) and we would only have to solve the second bug once. And that’s why you should strive to make code abstract: so you can write it once, test it once, fix bugs once, and use it often.

Packages

By creating classes and methods, we can reuse our code throughout our application. But many times you want to reuse code between applications. One example is a tool, maybe an XML or JSON parser, or a data mapper, that you’ve written and that you want to use in other applications as well. We’ve seen that copy/pasting, even between applications, is not practical. You can reuse these components by creating packages (Dynamic Link Libraries or DLL’s) that can be used by other applications. When you use .NET you’re actually just using packages Microsoft created. Just like with class design there are some best practices when releasing and using packages. In his book Agile Principles, Patterns, and Practices in C#, Robert C. Martin discusses these practices[5]. I’ll quickly summarize them here.

Reuse/Release Equivalence Principle (REP)

The Reuse/Release Equivalence Principle states that only what is released can be reused. A release is something that is maintained by another author or entity. The release is a product and you are the customer. Releases get updates, which may or may not be of interest to you, and you may choose to update or stick to the old version for a while. Clearly, copied code does not fit this description (as we’ve seen with DRY). As applications often consist of hundreds of classes and interfaces, releasing single classes is tedious and overwhelming. A package is a good candidate for release so everything that is released can be reused.

Common Reuse Principle (CRP)

The Common Reuse Principle is kind of like the High Cohesion Principle for packages. Classes that are packaged together should belong together. If you reuse one class in a package you reuse everything from that package. If any class in a package changes you need to update, revalidate your software, and redistribute your software. For this reason classes in a package should have a high cohesion (I’m talking about cohesion between classes, not per class). You won’t be very happy if you get an update and have to do all that work, but you don’t actually need the update. Of course this happens a lot in practice, since the alternative is having many packages, which is also not great.

It might be nice to mention that some script libraries, like jQuery UI (a JavaScript library), allow you to download only the code that’s necessary for your use. You can then tick the components you want and leave the rest. Of course, such practice is not possible for compiled packages because you compile everything or nothing.

Common Closure Principle (CCP)

The Common Closure Principle states that classes that change together should be packaged together. That is because if you change a class, or responsibility, you don’t want to update more than one package. You don’t want to have unrelated functionality in one package (CRP) and you don’t want a single functionality in more than one package (CCP).

Acyclic Dependencies Principle (ADP)

The Acyclic Dependency Principle is really not an issue in C# and Visual Studio. According to this principle, packages cannot depend upon packages that depend upon them, creating a cycle in your dependencies. Making such dependencies is not possible in Visual Studio, though. If you attempt to compile .NET code that violates the ADP, you'll get an error with a message that complains you are creating a circular reference. Let’s dig into it a little deeper anyway. Suppose you have four packages: Data, Business, UIComponents, and UI. UI depends upon Business and UIComponents, and Business depends upon Data.

Dependencies without Cycles

Figure 23: Dependencies without Cycles

If Business changes we know UI depends on it, so we should check whether UI still works. We know that UIComponents and Data remain unaltered though. Likewise, if Data changes we know we should check Business and UI, but not UIComponents.

Now suppose Data depends upon UI.

Dependencies with a Cycle

Figure 24: Dependencies with a Cycle

We have now created a cycle. If Business changes now we should check if UI still works, but because Data now depends upon UI we should also check Data. If UIComponents changes, you have to revalidate every package. This is tiresome for four packages, but in a real world application with tens of packages it’s pretty much undoable. In any case, it’s very poor design and prohibited by Visual Studio.

Stable Dependencies Principle (SDP)

Some packages are written once and rarely updated, while other packages change frequently. Think of the user interface: every new feature to an application needs a change in the user interface to support that feature. Your Data Mapper, hopefully, changes a lot less often. If a package rarely changes, it is said to be stable. According to the Stable Dependencies Principle, stable packages should not depend upon unstable packages. That makes sense because every time a dependency is updated, we should validate a package. That means that if a dependency changes often, we should validate often. By that logic a package is only as stable as the least stable package it depends upon, so stable packages should not depend upon less stable packages.

Stable Abstractions Principle (SAP)

For packages to be stable it is necessary that these packages have a certain level of abstraction. Without abstraction these packages could not be easily changed or extended rendering them unchangeable or unstable. We have already seen how we can create extendable software by using interfaces, inheritance, and SOLID principles.

Layers and Tiers

You will often hear the terms layered or tiered software design. A layer is a level of abstraction. A tier is a physical separation between software responsibilities. A client-server solution, for example, is a 2-tier architecture. If you decide to throw in an extra server for some business logic service, you have created a 3-tier architecture. N-tier architecture is relatively easy in object-oriented programming because modules can easily be reused on different tiers.
When you practice the principles laid out in this book a layered architecture will come almost naturally. A well-known layered structure is that of the OSI model, a model for describing the different layers of the Internet. Simply put, your browser has no idea how to send bits and bytes over a line, nor does your line know about any browser. Yet, through abstraction, both work together to present websites and applications. The same principle goes for software. Your application has no knowledge of a database, yet, through some IDataMapper interface, it is able to communicate with a data storage.

Such a layer can easily be replaced without affecting the rest of the system. When you look at the Internet, you can easily switch from a dial-up connection to ADSL to cable to wireless. No need to replace your browser or your computer. Likewise, TCP/IP, the protocol used to send and receive data on the Internet, can be switched for UDP, another faster, but less secure protocol for sending data. In your application, you can switch your databases, or rebuild your user interface, without touching the rest of the system (given that you’ve successfully separated concerns in different layers).

A common layered or tiered architecture may look as follows:

Common Layers or Tiers

Figure 25: Common Layers or Tiers

Architectural styles and patterns

All these patterns, layers, and tiers can be combined to create greater architectural styles and patterns that are not mutually exclusive. Picking what’s right for you and your project is extremely difficult and probably a matter of what you know and what you prefer. Sure, some patterns make more sense for specific applications or technologies, but overall you have some choices.

Consider an ASP.NET MVC application. The name already implies we’re using MVC. The MVC framework uses REST (Representational State Transfer, meaning, in simplified terms, that your server just sits there waiting for a request, handles the request, sends a response, and continues waiting). Perhaps you’re using AngularJS or KnockoutJS in the front-end, which is MVVM. You’re probably already 3-tiered (HTML, CSS, AngularJS/KnockoutJS on the client, a web server, and a database server). You can opt to create some additional services for business logic that you can use for other platforms as well. Additionally, the front-end might make use of an event-driven design.

As you see, you probably use a lot of patterns already and perhaps didn’t even know it. Discussing all these patterns individually requires another few books, so I won’t do that. Having these tough architectural decisions made for you by the framework or technology you’re using may be how you like it, or you may want to create something on your own. There are pros and cons to each approach.

The Takeaway

Object-oriented design should have no secrets for you now. You might be wondering how far you should go in abstraction and reuse. After all, just because you can doesn’t mean you should. It’s really hard to say where you should draw the line as it’s highly subjective. I once saw the following abstraction:

Code Listing 104: NowResolver

public interface INowResolver

{

    DateTime GetNow();

}

public class NowResolver : INowResolver

{

    public DateTime GetNow()

    {

        return DateTime.Now;

    }

}

Is this going too far? Has the programmer lost himself in a sea of abstraction? You might be tempted to think so, but actually this is pretty smart. Imagine you have to run some tests on a class that gives different results dependent on the current time (or date). That’s not uncommon at all; maybe it’s a financial application that needs to get the exchange rate at a given date. So try testing that if the codebase has DateTime.Now directly in the methods. With the INowResolver you can inject your now and test for yesterday, now, and tomorrow.

Personally, I’ve never seen abstraction go too far. I have missed lots of abstractions though. Sure, having many unnecessary abstractions is hard to read, but missing abstractions is hard to maintain. So the only advice I can give here is to keep thinking, come up with different designs, compare them using the tools I’ve laid out in this book, and use your best judgement. Luckily, some of the harder decisions are already made for you by the framework or technology you choose for your next project.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.