Software Project Architectures Explained - Part 1: 3-Tier (.Net and Java)
"Construction site in the city", by Anton Gvozdikov. Used under license.

Software Project Architectures Explained - Part 1: 3-Tier (.Net and Java)

Whether you’re a newb, a veteran or a business bod who just wants to know a little more about that stuff the devs are always spouting, here’s part 1 of a brief guide to software solution architectures!

3-Tier is a classic of organised code structure, and ideal for small to medium-sized projects that don’t involve corralling multiple back-end systems (use Service-Oriented Architecture for that).  

The three tiers build one from the other: data access at the bottom, where we talk to our data store (usually a database). Data access is in charge of how we talk to our data store and how we isolate that data from the rest of the project: our UI shouldn’t know (or be able to find out easily) what we do to write data away or retrieve it. 

The ‘next’ layer in the tier is the business logic layer. This is where we write our code to ensure that we validate, authenticate, log, process and manipulate data according to our business rules. This does the orchestration of how our project actually deals with data, but it has no clue how the data is stored and retrieved other then ‘I ask the data access layer nicely’.

Lastly comes the UI layer, which is in charge of putting data in front of the user and letting them interact with it. The UI has no other concern than the display and formatting of data- anything else it lobs to the business logic layer to handle.

Other concerns

IoC (Inversion of Control, implemented using Dependency Injection)

To keep layers safe from the impact of code changes (without the need for large-scale rewrites!) ensure that whatever a layer makes available to the project is an implementation of an interface, and that your classes all follow the principles of Inversion of Control. If a class A uses class B, then a reference to class B should be passed (injected) by the caller to class A’s constructor or method. Class A shouldn’t need to create a new class B, otherwise the two will become ‘tightly coupled’ and changes to one will have severe knock-on effects to the other.

Interfaces let us code like we’re using lego bricks: I can pop any brick into the gap as long as it’s the right shape. No need to worry about inheritance or rewriting code. They also give us a handy hook-in for creating test doubles using Moq, Rhino etc.

Open-closed principle

Try to avoid making changes to the signature or return types of a method. This can cause problems for anything that’s using that method, and those problems may not be immediately obvious (even with good test coverage). Instead remember “Old MacDonald Had a Farm, EIEIO!” That stands for Extend, Implement, Encapsulate, Inherit and Overload. Basically write something new that implements the old but overloads the bit you want to change. Optionally mark the original as ‘Obsolete’. You can then replace calls to the old method at your leisure with the help of Visual Studio or your favourite static analysis tool. Your code will compile and work and you can swap out the old code for the new as you go.

Testability

Recommendation

Use a test-doubles framework like Rhino or Moq to test each layer in isolation. The test doubles can stand-in for the other parts of your system by implementing your interfaces (remember those from Other Concerns) and you can test the whole lot in relative safety.

Try to avoid your tests being too granular- there is a happy medium between making sure your tests exercise enough to be effective but not so intimately tied to the code that they become brittle and tightly coupled.

Pro Tip

If you follow EIEIO then your tests won’t always break when you need to change a method signature or a return type! Write new tests for the new methods and the old ones will carry on going!

Bugaboos

Choose your data access technology carefully, or you’ll be stuck with something that you can’t test, unless you want to do integration tests against a database. That will end up with a heavy reliance on test data (either permanent data or setup-and-teardown).

For example, .Net SQL DBML files are terrible for this, with your only options being to hand-change a magically generated file (and losing the ability to let Visual Studio manage it for you) or write a wrapper.  

If your data store is the file system, then you can use a library like SystemWrapper to get some of the legwork done for you. Entity Framework is a better bet than DBMLs for being testable, but in honesty is still a bit of a nuisance. Ditto for Enterprise Library Data Access blocks. NHibernate is probably the best, although (regardless of project scale) NHibernate always makes me feel like I’m trying to catch a fly with a trebuchet. 

What won’t it save me from?

Business process changes. If your PM, BA or product owner walks up to you one day and says “oh, by the way, that code you’ve been writing for a new online financial management tool? We’ve decided it’s now a recipe site.”

I exaggerate, but fundamental changes to what your project is meant to do, in terms of big-picture workflow and process, will always be death. You can either rewrite from scratch or try to shoe-horn functionality into places it was never meant to go.

I've read some religious arguments that proper functional programming (usually in concert with React/Angular 2) are the elixir against this but trust me, the only real solution to these is to be agile. Properly agile. 

Sketch out the whole project, with the stakeholders. Let them organise the priorities and then start at the top. Plan it, document it, code it, test it and then demo it. Any changes or bugs go back into the backlog and then you get the stakeholders to reprioritise again. Wash-rinse-repeat until the stakeholders are happy for it to go live.  

Remember

Analysis must be ongoing: priorities change, businesses change and ideas evolve.  

Testing will never find every bug, nor is it supposed to: this is based on the theory (from The Mythical Man Month) that there is a minimum number of bugs in any system: attempts to fix them will only make more bugs, so the number won’t meaningfully change. Testing needs to be done as you go along with a view to the project being of a quality and standard that is high but not perfect. This also avoids cutting test time if the project runs late.

Coding changes as you delve into the project domain and learn more about what you’re doing in this specific instance. Use that power.

User testing has to be integrated into the flow, because it’s too late to change a product users hate once it’s all been written.

---

Look out for the next part, coming soon! Until then, is your software project burning a whole in your schedule, budget and sanity? Give BashCorp a call, we promise to only burn a whole in your sanity!

要查看或添加评论,请登录

Kay Elúvian的更多文章

社区洞察

其他会员也浏览了