Patterns primer
A quick jargon buster for common patterns and terms.
Object oriented programming (OOP)
- An object encapsulates state (data properties) with behaviour (methods).
If an object has just properties (a property bag) it can be called an anaemic domain model and may be an anti-pattern. - The encapsulation ensures some of the data and methods are public and some may be hidden (private)
- Objects may inherit from others. Subtypes may be polymorphic (a method can use a common supertype e.g. "cat" while accepting "tiger", "lion" etc).
Inheritance is problematic, and most recommend "composition over inheritance" (contain or wrap objects). - You may use polymorphism, where you don't use the exact type, but instead a subtype (inheritance or interface).
The capability is behind most OO recommendations to "code to interfaces, not implementations", Liskov, etc. - There are variations (class-based versus prototype based)
- There are alternatives:
- purely procedural code,
- declarative code ("what", not "how": xml, regex, sql)
- functional programming (declarative code that uses functions, avoiding state and mutable data). .Net lambdas and the strategy and visitor pattern are functional.
- Static typing vs dynamic typing and duck typing - runtime vs compile-time type checking. .Net is mostly static typed with "dynamic". The .net compiler has duck typing in that you can foreach over anything with GetEnumerator().
OO has potential problems. Doing too much inheritance, over-abstraction, encapsulating too much (god classes) makes things complicated. Hence the SOLID principles and design patterns (which in turn can be over-engineered).
General Principles
- Depend on abstractions. Don't rely on concrete types because they may change. Not to the extreme that everything has an interface (eg just for test mocks), or abstractions of abstractions (a repository around an ORM is probably dumb).
- Loose coupling : Another expression for the above. Loosely coupled systems allow individual types to change, or new ones to be added, without rewriting everything.
- DRY : Don't repeat yourself. Don't duplicate code. NB: Test code is usually not very DRY, as it's clearer to be explicit in each tests.
- Law of Demeter: Only use an object and it's direct methods, not indirect. Also "Don't talk to strangers" and "count the dots" (x = a.b.c().d.e() is dangerous)
- 3 tier/n-tier : classical Microsoft layers: UI, logic, data access. Easy to overdo this (project hell), but the principle is separation of concerns (separate UI from business from data).
- YAGNI : "You're not going to need it". Don't add stuff (esp extensibility points) unless you actually need it.
- Inversion of control (IoC) : Break complex dependencies by giving control to the consumer object, instead of the provider. IoC containers (dependency injection frameworks) make building dependency chains easier.
SOLID
An acronym for 5 principles of OO. Unfortunately even these can be taken to extremes, with "good" practices justifying over-complicated architectures. Only the "D"- dependency injection - is relatively unambiguous.
- Single Responsibility Principle (SRP). An object does one thing only.
But not too simple (DTOs, anaemic domain models). - Open/Closed (open for extension, closed for modification). A switch statement may violate OCP as you'll need to change it when there is a new value.
In practice, extend vs modify is a bit vague, and OCP can encourage over-engineering. - Liskov substitution principle. Allow replacing subtypes.
In .net, the common LSP violation is IStream, where some streams can't seek (hence the awkward Stream.CanSeek). In practice, rarely a concern. - Interface segregation principle. Use many specific interfaces over a general one. But too granular interfaces are also complicated.
- Dependency injection. Control dependencies by injecting them (eg constructor arguments).
Design Patterns
Patterns are reusable solutions to common problems. The patterns movement started with the "Design Patterns" book by the "Gang of Four" (1994). Follow links on Wikipedia.
- Creational:
- Factory : create objects without specifying exact type (enabling polymorphism). Eg WebRequest.Create returns an HttpWebRequest or a FileWebRequest.
- Builder : build complex objects (instead of huge constructors/factory argument lists). Eg ConnectionStringBuilder.
- Singleton : there is only one
- Structural (composition, "wrapping")
- Adapter and Facade present different interfaces (in .net, calling COM is via an adaptor wrapper)
A facade is a simpler interface; adapter meshes multiple interfaces. - Decorator : wrap classes with the same interface, adds behaviour in methods which can call inner wrapped instances
Decorators can be nested like Russian dolls (chaining).
A decorator (and proxy) has the same interface as the subject; facade/adapter have different interfaces.
- Adapter and Facade present different interfaces (in .net, calling COM is via an adaptor wrapper)
- Behavioural:
- Iterator is built into .net (IEnumerator, foreach)
- Command is self explanatory
- Strategy : pass a method/function which does processing. In .net, List.Sort(IComparer) is using strategy.
- Cargo cult programming - using patterns without understanding (see also architecture astronauts, who over-engineer).
- God object (often "xManager") - an object that does everything
- Anaemic domain model- no logic, just properties (DTOs- data transfer objects - can get away with this)
- Sequential coupling - methods must be called in a specific order.
- In .net, try/catch(ex) { throw ex } throws away the stack
- In javascript, putting everything in global scope.
Other patterns
- 3-tier/n-tier (layers): Typically a web (or windows) solution would have 3 core projects: domain/model, data access and UI. In other words, separation of concerns. A very old but still well-used pattern. A variation is hexagonal architecture (aka "ports and adaptors"). The domain is in the centre, with available ports (contracts/interfaces). Plugging into the ports are adaptors, which are the data access and UI.
- Service oriented architecture (SOA) : loosely coupled components ("services") performing self-contained actions. In practice, interaction becomes RPC (remote procedure call) using SOAP (xml) messages.
- MVC : model view controller, a pattern for separating UI code.
- MVVM : model view-model model is a variation of MVC, initially WPF and now most javascript SPA frameworks. The view-model exposes the model for data binding (data binding is the controller).
- REST : Representational State Transfer has resources (URI) which use standard methods (HTTP verbs, GET, POST) as opposed to the RPC style of SOAP.
REST is not CRUD (although WebAPI makes it look like it)- resources aren't entities, and there should be no fixed resource names. Versioning may be better expressed as new resources, not HTTP headers/URL version numbers (this is controversial!).
In practise, there are compromises (Http DELETE and PUT are rare, PATCH is only proposed), and a lot of scope for discussion over exactly what a resource is.
Some Rest enthusiasts("Restafarians") insist on PUT for new records, POST for updates, but others say either is ok, as long as PUT is idempotent (can be called multiple times safely).
In the Richardson maturity model, the most RESTful API has hypermedia controls, which have links for what to do next (HATEOAS). Ideally, you just know the initial URL and every other URI can be discovered. - CQRS : Command query responsibility segregation. Keep commands (inserts) separate from queries (reads). Useful when contention over limited data (forces asynchronous flow- to ask if command worked, you poll with queries). Often used with event sourcing (see below), which solves contention by being non-blocking append only.
- Event sourcing : Each state change raises an event, which is saved in an event store. The log of events can be queried (tracking), replayed (subscribing UI), or reversed (roll back). Writing is simple (a stack), but reading may require a snapshot aggregate record (perhaps with a delay- "eventual consistency").
- Distributed computing : See the famous Fallacies of Distributed Computing. Some of this, in the modern world of HTTP, is less likely to be a fallacy (we know the network is not reliable, bandwidth is not infinite).
For reliability, use multiple services/servers (active-active with load balancing, or active-passive with failover)
On failure, retry with exponential back-off algorithms can be used for transient errors; the circuit breaker pattern can help for longer lasting dependency failures. - There are a number of patterns to support testing. Arrange-Act-Assert (or BDD reformulation, Given-When-Then) is the standard structure. To make code testable, dependency injection and mocking are often prescribed (and over-used), as well as simple "extract and override" and dumb stubs. The "pesticide paradox" is that static test data only exercises specific paths; other bugs take over- so tests must be regularly revised.
- Cloud services:
- IaaS - Infrastructure as a service: Virtual machines. Close to traditional hosting.
- PaaS - Platform aas: cloud services/websites, provisioned and updated for you.
- SaaS - Software aas: a service API or website.
Approaches
- Waterfall and agile -
- Waterfall is the traditional project management approach- analysis, design, code, test, install. There are some versions with very formal stages and job roles like SSADM and Prince2.
- Agile is a more modern (since 2001) approach, the most well-known version being scrum. Requirements go into a "backlog", and are processed in short iterations ("sprints" in scrum), which are incremental. Unlike waterfall, plans are expected to change and evolve (although a sprint is fixed). Scrum has a scrum master and daily stand-up meetings, and can become very formal (and not particularly agile, which is supposed to emphasise people over process). There's even a Prince2 for agile.
- Lean is an agile "just in time" approach. It uses short iterations with customer feedback, driven by the "empowered" developers.
- Kanban is a work progress approach, often associated with agile (it is an alternative to sprints). Trello is one website version; physical boards with post-it notes are common. The project is a board with columns (minimally, Todo-In Progress-Done) and horizontal swim-lanes (often, "urgent"-"normal"-"blocked", otherwise individuals or user stories). It is easy to view work moving left to right.
- TDD - Test driven : design tests first or at the same time. It helps define APIs as the tests use the API.
- BDD - Behaviour driven : a rework of TDD to define user stories (as a X I want to Y) with scenarios (Given - When - Then). Specifications are lower level (technical) than stories.
The most common language is Gherkin (used in Cucumber). Jasmine and Chai are javascript testing frameworks, .net has SpecFlow and NSpec.
In practice most people name their *Unit/Test tests in BDD style (Given_When_Then) even if they don't do BDD. - DDD - Domain driven : The domain is modelled in a "ubiquitous language", with sub-domains as "bounded contexts". Entities are defined into groups with an "aggregate root" which is the only externally accessible entity (Order has orderlines, but you must always load the order first)
Don't forget
No application/service is production-ready without some these:
- Documentation. At least a technical "read-me" (volumes of comprehensive text will be ignored). Must be up-to-date.
- Developer Tests (unit and/or integration as applicable and practical).
- Some kind of performance profiling. From manual acceptance tests to NewRelic type analytics.
- Monitoring. From simple pings to detailed analysis. For historical data and alerts when it dies.
- Logging. At least exception logging.
- Security. Even inside an organization you need some.
- Build process. Visual Studio/Source control/build server may be enough, but you need to be able to produce a repeatable deployment package.
- Deployment plan. Plan for initial deployment as well as upgrades, server maintenance and server moves.
- Dependencies: You need an agreed list of core dependencies.
- IDE (Visual Studio, Resharper), source control (git, svn, TFS), build servers.
- Data access e.g. ORM like Entity Framework or Dapper, or raw ADO
- UI e.g. Winforms, MV Razor, an SPA like Angular...
- Dependency Injection e.g. StructureMap
- Logging e.g. log4net, NLog, Serilog, EntLib, .net Trace. "Common logging" is normally an abstraction too far.
- Exception logging e.g. ELMAH
- Tracing e,g. Glimpse
- Testing framework e.g. MsTest, XUnit, NUnit. Some like extension assertions like Shouldly (some, like me, dislike them)
- Mocking (ideally limited, but probably required) e.g. Moq
- Others: AutoMapper, Swagger ...
The 4+1 architectural model is the most common "view model" where there are multiple views of the system: logical (class and sequence diagrams), implementation (UML component diagram), process (UML activity), physical (deployment), plus use cases.