Separating applications into separate layers or tiers has long been touted as an architectural best practice, but the typical naïve approach can result in tight coupling and a heavy dependence on the database. Alternative approaches to layered architectural design do not suffer from this limitation, allowing for loosely coupled, testable solutions that can swap alternative infrastructure implementations in and out of the application.
In this session, we’ll examine the traditional N-Tier design and its deficiencies, and introduce an alternative based on the Ports and Adapters or Onion architectural pattern, and we’ll show how to set up a solution from scratch to take advantage of this approach.
Learn more here:
http://pluralsight.com/training/Courses/TableOfContents/n-tier-apps-part1
9. More Principles
• Separation of Concerns
Establish boundaries to separate behaviors and
responsibilities within a system.
• Dependency Inversion
Depend on abstractions, not concrete
implementations.
11. Testability
• Testability correlates with:
– Loose coupling
– Separation of Concerns
– Maintainability
• You don’t have to have unit tests to have
testable code!
– Unit tests prove (or disprove) testability
15. Incremental Improvement
• From a Single Project
– Separate responsibilities using folders
– Add Project(s)
• From Many Projects
– Create a new Solution with only what you need
– Spin off separate applications into own solutions
17. Problems
• Tests are still not unit tests
– Tight coupling
– Static Cling
– Dependence on Data and Email Infrastructure concerns
• Nothing to prevent improper referencing of classes
– e.g. UI code can call DAL code directly and bypass business
logic
• UI Content Folders mixed with Application Code
Folders
18. Onion Architecture
• aka Ports and Adapters,
• aka Hexagonal Architecture
• Core models and interfaces at center
• Infrastructure and Implementation depends
on Core
• UI depends on Core and has access to
Infrastructure
21. Results
• Yay! Unit Tests that work!
• Boo! Still nothing to prevent improper
referencing of classes
– e.g. UI code can call DAL code directly and bypass
business logic
• Boo! Still mixing content and code folders
27. References
• Separation of Concerns - http://bit.ly/zWujRe
• Hexagonal Architecture - http://bit.ly/8Uxl5
• Onion Architecture - http://bit.ly/4tWMT3
• More Onion Architecture - http://bit.ly/NzF2Sz
• New is Glue - http://bit.ly/Ijn98e
Pluralsight Resources
• N-Tier Application Design in C# http://bit.ly/Msrwig
• Design Patterns Library http://bit.ly/vyPEgK
• SOLID Principles of OO Design http://bit.ly/OWF4la
28. Thanks!
• Find me at http://ardalis.com
• We’re hiring developers and trainers at
Telerik!
• http://telerik.com/company/careers.aspx
Editor's Notes
This image represents the problem, or at least one of the major contributors to the problem. Visual Studio’s getting started experience is entirely focused on projects – there are 3.2 trillion project templates installed with Visual Studio – and one solution template.Visual Studio offers absolutely no guidance when it comes to structuring Solutions. As a result, there is a great deal of variation in the way different individuals, teams, and organizations approach this problem. In some cases, choices made at the solution level can have a significant impact on the maintainability of the resulting application and its structure.This is not just an ASP.NET or MVC concern, but we’ll look at a solution to the Solution problem in the context of ASP.NET MVC 4.
Sometimes improper solution organization results in too many projects. The problem with too many projects are slower build times, slower load times, issues with some analysis and plug-in tools (usually perf issues), and difficulty finding where the actual code relevant to the issue at hand is located.I’ve seen organizations that have ten years’ and dozens of separate applications, from web sites to web projects to console apps to winforms apps, all in the same solution. The result is a solution with around 100 projects that if you ever accidentally Build, you better go get a cup of coffee, because VS isn’t coming back for at least 5 minutes.The problem with too few projects is that there isn’t enough separation of concerns, and frequently the result is greater coupling within the codebase and frequently a big-ball-of-mud or spaghetti code style of coding with insufficient abstraction.For those solutions that do make use of multiple projects, sometimes a lack of abstraction and application of principles can result in tightly coupled code despite separation into separate projects.(ask if they know story of the three bears)The ideal or “goldilocks” solution has not too many, and not too few, but just the right number of projects.
These are some of the principles to keep in mind when considering how to organize the projects within your solution. Some of these should be familiar to you. Many of these can be found in more detail in the book, Principles, Patterns, and Practices of Agile Software Development in C#.It’s important to remember that Projects are not simply high-level folders for organizing your code, but also correspond to deliverables in the form of .NET assemblies. In larger software systems, if they are well-composed, it should be possible to deploy a bug fix or add a feature by replacing just those components that have changed, without having to redeploy the entire application. This reduces the impact of the change on the system and the likelihood that bugs are introduced by the change.
Common ClosureThe classes in a component should be closed together (see also the Open/Closed Principle) against the same kinds of changes. A change that affects a component affects all the classes in that component and no other components.If we must change the code in an application, we would prefer that the change occur in a single assembly, rather than being deployed across many assemblies. Thus, this principle suggests that if two classes are tightly bound, either physically or conceptually, they’re likely to always change together, and thus should belong in the same component.
Common ReuseClasses tend to collaborate with other classes. It makes sense to package a class and those it always or frequently collaborates with together.
A module (or class) that you have designed to be easy to change can suddenly be made difficult to change by someone else hanging a dependency on it from a hard-to-change component. Now, any time you make a change, the dependent code must also be updated, and your easy-to-change class just got harder.How do we define stability? Essentially if a class or component has many incoming dependencies, it is very stable, because it requires a great deal of work to reconcile any changes among all the dependent components. We can measure stability in terms of the number of inbound and outbound dependencies a class or component has (also known as afferent and efferent couplings).Some software in the system should not change very often, such as high level architecture and design decisions and core business rules. These should live in stable components, meaning they ideally should have no outbound dependencies and many inbound dependencies.Of course, we also want these rules and decisions to be flexible, and we can achieve this via the Open/Closed Principle and the use of abstractions.
A component should be as abstract as it is stable.That is, stable components should be abstract; instable components should be concrete. Thus, assemblies with many inbound dependencies should be more abstract, while those with few dependencies should be concrete.We can graph the relationship between abstractness and stability as shown here (click).In the top left, we have components that are maximally abstract (nothing but abstract classes and interfaces) but have no packages depending upon them. By definition such packages cannot do anything.In the bottom right, we have components that are very stable, thus they have many dependencies upon them, but they are not abstract at all. This is known as the zone of pain. These components are difficult to change because of their incoming dependencies, and cannot be extended because of their lack of abstractness. A very common example of a component that would fall into this area would be a set of classes that represent database schema. Database schemas tend to be very volatile, very concrete, and highly depended upon, and this is one reason why schema updates are often so painful for software applications.
Separation of concerns is a more general principle that applies both to components and packages as well as to classes and methods. It’s somewhat related to the Single Responsibility Principle, in that if we have multiple responsibilities or concerns comingled together, it’s best to separate them. How we separate them will vary depending on context, and might be anything from creating a new method, a new class, or even a new assembly.The Dependency Inversion principle is the object-oriented design principle that the Stable Dependencies and Stable Abstractions principles derive from. It suggests that your dependencies should point toward abstractions, not concrete implementations. A related technique with a similar name is Dependency Injection, which is also related to the Strategy design pattern, and we’ll see this in action in a few moments.
Demo Script:Hg up 10 --clean0) Make sure Smtp4Dev is running/listeningRun the GuestBookMvc4 ApplicationAdd a couple of entriesShow the HomeController methodsAsk the audience what’s wrong with this setup? What would you change and why?
Likewise, you don’t have to have unit tests to have correct code; but unit tests can prove (or at least provide strong evidence) whether the code is correct.
Let’s try and add some unit tests to this simple solution and see how well that works for us. Remember that a unit test should be able to just test the logic of the class or method under test in isolation. It shouldn’t need to talk to a database, file system, network, or other infrastructure. If it does do these things, it’s a different kind of test; it’s an integration test.Demo Script:Open the unit tests projectWalk through the first test and run it (SetMessageAndInitialViewModel)Walk through the second test and run it (make sure Smtp4Dev is not running)It should fail with an SmtpExceptionLook at the code where it failed – sigh…Run Smtp4Dev to get it to work.
A useful free tool for doing integration testing with email sending is smtp4dev, which you can grab at codeplex at this URL.
When we have an existing application that isn’t set up the way we would like in terms of its solution, project organization, or architecture, we usually want to try and follow the Boy Scout Rule. That is, with each checkin, try to leave the code better off, cleaner, than you found it.
Assuming that you have an existing solution that is causing you pain because of its organization, there are incremental baby steps you can take to improve the situation. In this way you can avoid a complete reorganization of the codebase, but still follow the Boy Scout Rule and leave the code better than you found it.If you have a single project without any separation of concerns (like my Guestbook currently), you can start to separate responsibilities into different folders within the project. Eventually, you can move the classes from these folders into their own projects.If you’re at the opposite end of the spectrum and you find that you have too many projects in your solution, the simplest way to move forward is to create a new blank solution and add in just those projects you’re actively working with for a given unit of deployment (typically, an application). Generally I’ve found it’s best to have a separate solution per unit of deployment, though it’s perfectly acceptable to share code and projects between such solutions. If you worry that a breaking change in a shared project will go undetected in a separate dependent front-end application, that’s what your automated build server is there for. Lacking one of these, you could easily write a script that verifies all solutions work with the latest code and run this whenever you feel the need to do so, without cluttering Visual Studio with dozens of projects and slowing down every build in your IDE. We’ll look at writing such scripts in a few moments.
Hg up 12Demo Script:Show BLL folder and contents – Guestbook and static methodsShow DAL folder and contents – just the DbContext class, no need to open itNote that BLL RecordEntry still has mixed layers of abstractionDeals directly with DbContext internals twiceRequires multiple steps to save an entryRequires a bunch of low-level code to send emails in a loopFix by creating a new Data Layer abstractionMove Email to a Static UtilityHg up 13 (has these changes)
We’ve separated some concerns and made the code at all layers easier to read. The code is definitely better organized than when we started.However, we still have some problems. We have our two tests, but they’re still integration tests that rely on infrastructure, not unit tests. We’re still depending on implementations. We still don’t have any abstractions at all to speak of. We have a lot of tight coupling as a result of our use of static method calls and instantiating classes directly in our methods (remember, New is Glue).There’s also nothing to prevent our UI layer from doing an end-run around our Business Logic Layer aside from programmer discipline or manual code reviews. Ideally we would like to make it difficult for code at one layer to depend on code at another layer that it shouldn’t. One way to achieve this is to minimize how much code is in the UI layer, do as much work as possible in our business logic classes, but avoid having these depend on implementation details and infrastructure. We’ll see how to do this in a moment.Finally, if we look at our project’s folder structure (click), we have all kinds of concerns mixed at the same level of hierarchy. Some of the folders are server-side code, others are client-side resources, etc. We always have some of this in an ASP.NET MVC project, since it has a folder for Controllers, and frequently for Models, but we don’t need to make the problem worse if we can avoid it.
The design we’re going to consider now that follows the principles we’ve discussed and the problems we’ve encountered goes by several names. I tend to refer to it as Onion Architecture, because it is frequently depicted as a cross-section of an onion, with many concentric layers (as anyone who has seen Shrek knows, Onions and Ogres have layers. However, the Ogre Architecture name didn’t get much traction).
Move BLL.Guestbook to Core.Services.GuestBookServiceRemove StaticsAdd Interfaces for Getting list of emails to notifySending notificationsPersisting the entryALTERNATE DESIGN:Expose an event that fires whenever a notification needs to be madeHave a Notifier subscribe to this event or pass in a Listener to the classHg up 15Show HomeController and Dependency Injection via StrategyRun unit tests – they still send email to smtp4dev (or fail)Hg up 16Show fake email implementation in tests
Hg up 17BUILD – Tests failing because they are talking to infrastructure (remove namespace and ResetData())Note that HomeController.Index() is still talking to Infrastructure (fix on line 31)Hg up 18
Hg up 19Show build.proj, build.bat, clicktobuild.bat
Hg up 20Actual unit tests of the logic of each component.Refactored NotificationService to extract the filter policy to make it easier to test on its own.Hg up 21 – add a test for the date range in the NotificationPolicyIt fails!!!!!!!!!!!!!!!!! What? We’ve been running this thing, with tests, this whole time, and that part fails???Fix it.Hg up 22