Here’s some information on things I’ve used while helping deliver software projects. It’s by no means an exhaustive list, and will of course change over time. I’m selecting what gets included/excluded based on my perception of what people tend to look for or ask about; if something’s not in here it doesn’t necessarily mean I’m not familiar with it or haven’t used it as part of a project – just that I have to draw a line somewhere, and a list of every tool or NPM package I’ve ever used would be a bit crazy. I also strongly believe in continuing to learn, and that the ability and willingness to learn new things is far more valuable than the current sum total of knowledge. When selecting projects to work on I look for a balance between things I’ve already worked with (and found useful and worthy of working with again), and things that I’ve perhaps not used before but am interested in. I try to stay near the “sharp edge” of the technology curve, experimenting with new platforms and emerging technologies, and combining these where appropriate with more established things based on how they fit the task at hand.
- Microsoft Azure
- Cloud Services
- Blob Storage
- Table Storage
- Azure Service Bus
- Amazon Web Services
- S3 Storage
- SQS Queues
- Microsoft Azure
- ASP.Net MVC
- ASP.Net SignalR
- ASP.Net WebAPI
- Web/Desktop Client
- Octopus Deploy
- SQL Server
- Entity Framework
- Key-value storage
- SQL Server
Strongly Typed Languages
The language I currently enjoy working in the most, and feel most productive in, remains C#. This is especially so since the cross-platform .Net Standard, and latest round of language enhancements. I enjoy working in F#, and am a little envious of some of the language features F# has that haven’t made it across into C# yet – particularly Discriminated Union types, but it feels like C# is evolving in a very healthy direction.
I’ve been delivering projects in C# since .Net 2.0 and all the language revisions in that time, and the single change in that evolution that has made the biggest difference in my mind is the introduction of Lambdas and LINQ; a close second more recently is the async/await model, and I think the next big winner will be the newly introduced pattern matching.
The Roslyn compiler is also a major advance for the .Net ecosystem. It has made it far easier for developers to customise their tooling, with analysers and compiler services functionality. This in combination with the powerful meta-programming in the framework itself make it possible to elegantly handle cross-cutting concerns via techniques such as Aspect Oriented Programming. I still use the ReSharper plugin and Visual Studio, but feel it may get edged out by the advances in Roslyn and in other editors, notably VSCode and Atom.
I still consider C# my “primary” language, and all the above is only scratching the surface of the things that are possible with the language. My preference is swinging towards functional programming over object orientation, but C# is a multi-paradigm language that handles both well. I’d like to see more support for immutability by default (although there are ways to achieve this with a little effort), and I’d like to see (more) support for different threading/process models – I’d love to see an Erlang-like lightweight process and concurrency model and runtime underneath the C# language.
The DotNetCore frameworks represent a major landmark for the .Net framework, and I’m enjoying working with them. The context in which I’m using these in production applications is in AWS Lambda functions. The launch of DotNetCore hasn’t necessarily been the smoothest, with a lot of churn in the release candidates, but I wouldn’t hesitate to go straight for DotNetCore in any new project now.
The F# language is Microsoft’s functional language on the .Net Core Language Runtime. I’m using it in my own personal projects, but haven’t yet had a client project where it’s been used – although I’d like to, in the right circumstances! I’ve been using the Project Euler challenges as a set of problems to solve (just for fun) in F#, which are a nice fit as they are algorithm-heavy.
Where F# is ahead of C# is in writing very expressive code, and its approach of immutability by default. The way that dependencies are modelled in F# makes complex or circular dependencies impossible, and the language places a high value on simplicity and being easy to reason about.
I also really like the F# type system, especially Discriminated Union types, which make very rich domain models possible, where invalid states of the application can be prevented at compile time. F# makes entire categories of error impossible, which I think makes it very valuable.
I think I would introduce F# into a C# environment as a way of modelling the core domain; I’d probably stick to C# for infrastructure code and wireup because the tooling support is better at the moment. It would be nice to see Microsoft treat F# as an equal citizen in the .Net ecosystem; at the moment it is a bit of an outsider, with most tools being developed by the community.
I am continuing to build personal projects in F#, particularly for things like modelling board games, which can have intricate rules. I find that F# offers nice ways to express these.
The big win in using TypeScript is in codebases that are of significant enough size – which in practice will mean any serious codebase with multiple contributors. It catches a lot of bugs without even having to run code, by type-checking and performing static analysis. It requires a little investment to setup, but delivers a lot of value in enabling teams to work on the codebase with confidence.
ES2015/ES2016 and beyond
MicroServices/Service Oriented Architecture
Service orientation and microservices are all about being able to sub-divide a larger system into smaller components that represent logical boundaries within the application. I’ve been working with various applications in this style now, and I’ve seen a lot of stuff that’s worked and probably more that hasn’t; one of the ways that I engage with teams is as a consultant to help with projects in this style that have encountered problems as they evolve. The key to successful delivery of a microservices project is to constantly assess whether the boundaries between components are correct; it is very easy to get these wrong and pay a large penalty for it. Keeping boundaries well-defined, and managing the coupling across these boundaries to a minimum, via strict patterns, is vital.
The first service oriented system I was exposed to was a large e-commerce system, handling the processing of customer orders from purchase through to dispatch. One thing that was good about this prticular system implementation was that the services that were in place when I started on the project modelled autonomous business divisions that already existed; this meant that the service boundaries and the contracts for interaction between the services were already quite well-defined by the real-world processes the system was enabling. One of the weaknesses however was an over-reliance on Commands over Events, which lead to an artificial coupling and leakage of domain knowledge between boundaries. I worked with this team in alleviating some of the worst effects of this issue, by replacing Command-centric flows with Event driven alternatives.
I enjoy working with service oriented or microservice architectures, but there is definitely an anti-pattern in industry of adopting these where they are simply not needed. I strongly agree with Martin Fowler’s first law of distributing computing – “don’t do it unless you really know you need to”. I think microservice architectures can emerge naturally with boundaries that are reasonable by building a monolith application first, but with an obsessive focus on minimising coupling, and evolving as and when there are clear components that can be split out. Trying to retrofit service boundaries to a monolith that has not been designed with minimal coupling from day one is far harder, and may not be viable. The other mistake I see is the belief that separately deployed components are automatically not coupled; this fallacy can lead to major problems in the development flow of a team, as the coupling tends to be harder to detect and manage, and the result is instability.
When building services I believe in giving each service total autonomy, from data upwards. Communication between services is limited to asynchronous messaging, with no runtime dependency on any other service. This ideal isn’t always possible, and there may be limited exceptions where the pattern has to be broken, but aggressively targetting this ideal and keeping these exceptions to a strict minimum is needed. I think for a team to successfully deliver an application with this architecture, all team members have to have a deep enough understanding of the paradigm to recognise when something will break it, and whenever this is the case the team should huddle to discuss the proposed change.
The main technologies I’ve used to deliver microservices architectures have been NServiceBus and Azure Service Bus. I believe in keeping these firmly as infrastructure concerns, and in keeping the core domain pure. It should be possible to easily swap out the framework of an application with minimal change to the application’s core. I like to use queues (such as MSMQ, AMQP, Azure Storage Queues or Amazon SQS) or streams (such as Amazon Kinesis or a simple feed) as the underlying transit for event communication between services, and HTTP as the transit for commands.
Domain Driven Design
An understanding of Domain Driven Design is almost a pre-requisite to successfully build an application in a service oriented or microservice architecture. Modelling a domain in terms of entities and value objects, aggregates and events is an effective tool for identifying subdomains, which may be candidates for service boundaries – or bounded contexts.
One of the main things that is learned/unlearned in gaining an understanding of domain driven design is to flip from thinking about systems with commands, i.e. a linear workflow of each thing telling the next what to do, to events, i.e. things responding to things that have happened. This helps to limit the implied knowledge components have of other components in the system, and hence the coupling, leading to boundaries that are clearer and more autonomous.
Event Driven Architecture
Event driven architecture is a style in which interactions in a system are modelled primarily as rich events, rather than primarily as state. The events in a system describe changes that have happened over time, which roll up into a current state; however it is also conceptually possible to reconstruct previous states of the system from the events.
Events represent a particularly nice model for interaction between services, because they are far more decoupled in nature than commands. Events are multi-cast, i.e. many consumers can subscribe to an event, and the publisher of an event does not need to logically know about the identity of the consumers or how they consume the events; it is an infrastructure concern to handle the transit of an event. If events are distributed via queues, in a bus or broker pattern, the infrastructure will hold a directory of subscribers, but in an event stream, the publisher is completely disconnected from consumers – this has become my preferred approach now when architecting this type of system.
I’ve implemented several event-driven service-oriented systems now, including a large e-commerce platform for a major retailer, a healthcare system, and the back-end services of a AAA game title. There is also a parallel between event-driven service architecture and the reactive style of client development, which is a design style I’ve had success with in writing both web and desktop clients.
Object Oriented Programming
The object oriented model is the de-facto standard for the majority of projects, but is often not deeply understood. Object orientation can cause complexity challenges, especially if the wrong abstractions are applied. Designing objects according to well-known principles, such as the SOLID principles, goes some way to mitigating this. One of the criticisms of object orientation is that it is designed by definition to encapsulate, and hence obscure, state, while at its core all programming is about the manipulation of state. It is interesting to me to observe both a shift in popularity towards functional languages, and a shift in the design of traditionally object oriented languages to include more features from functional languages.
To make an object oriented design work well, it is critical to separate components cleanly between logical (domain) concerns and infrastructure concerns. Examples of this would be things like the “thin controller” in MVC architectures, or the “ports and adapters” pattern. The job of infrastructure or adapters is simply to marshall raw requests and responses, and transform these into calls into the core domain. This isolates these from each other, and makes for far easier testing. One of the most common problems I help teams to solve in object oriented codebases is leaking of infrastructure into the domain.
Every project I’ve worked on has used object oriented languages and paradigms for at last some part of the codebase.
In contrast to object orientation, functional programming elevates functions to be first-class components. This is often combined with language features like immutable data structures, and makes parallel computing much easier because code is guaranteed to have no side-effects, and functions can be passed around in the same way as data. The separation of state and data feels like the logical continuation of separation of concerns and single responsibility principle.
Pure functions are described as those that have no side-effects or external dependencies, and act solely to transform the data that comes in into data that is passed out. Writing the core domain in particular in this style offers significant benefits; it is much easier to reason about the effects of such functions, and they are almost trivial to test. The trick is to tease out pure functions, pushing infrastructure or side effects as far towards the boundaries as possible, so that the pure area is as large as possible.
I believe that with the trends in hardware and platform development towards parallelism and scale, functional programming will continue to grow, and I look forward to embracing it more in the projects I’m inolved with.
The reactive manifesto is about designing systems that are composed of components that subscribe and respond to events, rather than the conventional model of sequential coordination. There are advantages and disadvantages to this approach – as with any. A disadvantage is that the mental model is different, and it can be harder to directly see workflows – and so reactive architectures work best when there are natural boundaries and subdivisions, rather than simple workflows. The advantage is that components can be split out, and freed of coupling more effectively than with some other techniques.
There are a set of Reactive libraries, across many languages, that offer a standardised version of the patterns involved, the most common of which is the observer pattern. The Reactive libraries deal with the publish/subscribe behaviour nicely, and offer a ready-to-go implementation. They also offer a very rich Linq style way of interacting with event streams, and this is where the real power kicks in.
The Reactive paradigm can be applied nicely to UI design, as an alternative to manually coordinating threads and callbacks. I’ve built a responsive client application in this style, on top of the ReactiveUI library, which offers a way to treat UI events as streams in the model, giving clean separation from the view in a way that is easy to reason about.
The Reactive model also gives a nice declarative way of coordinating asynchronous work, which can be used in place of the Tasks Parallel Library in a C# or .Net context. The advantage is that the abstraction is at a higher level, and the details of coordination and the actions being performed are kept separate.
I’ve also used the Reactive paradigm to build a server-side long-polling system, similar to SignalR. The publish/subscribe behaviour of the observer pattern allows a clean way to deal with the connection lifecycles of clients, and with background changes that are to be published. This gives something close to a real-time data response, by broadcasting the event in HTTP responses as it arrives.
The actor model is not a new idea by any stretch, but one that is enjoying a renaissance at the moment, as trends in hardware are towards parallelism rather than raw speed. The actor model gives each “actor” its own process model, where it is completely isolated from all others, and can communicate only by messages. Popular examples include Akka (Java), Akka.Net (.Net port of Akka), Orleans (cloud-based virtual actors) and of course Erlang and Elixir, where the Erlang runtime is built from the ground up to be actor based.
Actors are all about concurrency, in that every actor is guaranteed to be single-threaded in handling its messages. This means that entire categories of race-hazard and mutable state problems are simply not able to happen. The messages
My main commercial experience with the actor model is in designing and implementing an Orleans-based system. Orleans is a .Net/Azure technology, that was famously used in the Halo games for managing online lobbies. The beauty of Orleans is that it gives a familiar programming experience, but offers the very powerful actor model. Each Orleans “grain” (actor) is kept on a server, but the management of those servers and their addresses is managed by the framework. Actors can keep their own state, which they hold both in-memory and in storage. Orleans keeps “warm” actors in-memory – if they are used recently – and saves their state to wherever you specify when they have not been used for a while, freeing up the compute resources.
SOLID Design Principles
The SOLID principles describe a set of good practices for object-oriented design. They are principles rather than rules, in that there are cases where it makes sense not to follow some of the rules 100% strictly. The SOLID principles tend to produce clarity in design of components, and to result in testable code.
The principles are Single responsibility, Open-closed, Lyskov substitutability, Interface segregation and Dependency inversion. Each of these has nuances to it, which are frequently misunderstood – perhaps Dependency Inversion is the most commonly misunderstood, because of the prevalence of Dependency Injection libraries and frameworks that don’t always encourage the principle to be applied correctly.
The single responsibility principle is about giving components a single reason for change. By extension, when a single change is made, the number of components that need change to accomodate that change should also be minimised.
Open-closed principle is about managing the extension of modules; one way to embrace open-closed principle is via inheritance; where a class that can be extended by inheritance is closed to modification (the original is unaffected) but open to extension (the inheriting class can customise or add behaviour).
The Lyskov substitutability principle is that implementations of a base-class or interface should be interchangeable. One way to break this principle would be by not implementing some of the methods, or by requiring the calling component to have knowledge about which implementation it is calling into.
The interface segration principle encourages the use of many small discreet interfaces over big interfaces. This is linked to the single responsibility principle in the push for small simple components, but is a little more subtle. If interfaces are broken up to be as small as possible, a class might then implement a combination of these interfaces. The counter-example would be something like the ASP.Net 2.0 Membership class, with lots of methods that any implementer is burdened with that mostly aren’t used.
The dependency inversion principle is linked to the dependency injection technique, but often used interchangeably with this – which is incorrect. Dependency inversion is about how dependencies are structured rather than how they are resolved, with the emphasis being on yielding the decisions about dependencies to the host application. Dependency inversion encourages a coupling by abstraction rather than implementation – that is, a class that depends on another component should depend on an interface that could be implemented in many different ways, rather than on an implementation directly.
There’s a witty observation that while Object-Oriented programming relies on these principles, the same problems are all addressed very simply in the functional programming paradigm by the use of higher-order functions! It’s certainly true that without skill and discipline, and understanding and applying principles such as SOLID, the design or object-oriented systems can be prone to problems.
One of the ways I particularly enjoy helping teams is when I get the opportunity to help a team with a project that would normally be out of their comfort zone. For example, I often work with teams whose background is developing desktop applications, who are now tasked with writing a web-based system, or teams who are new to things like service oriented architecture/microservices, or new to a different data paradigm. In these teams, my role is as much as a coach or mentor as technical, and I treat it as a primary outcome to make sure there is a healthy culture of learning and sharing knowledge in the team.
For some teams, this can be a scary thing – they might have slipped into a “comfort zone” for some time, and be out of the habit of learning. In some company cultures, driven by pay rises and fear of redundancy, admitting a knowledge gap can be dangerous, in perception, reality or both. I work with the leadership of these teams to encourage the right mindset of helping the team to learn and grow in a safe environment, driven by curiosity and enthusiasm over fear. In some teams there can be a dysfunctional “heirarchy” that the individuals try to climb, where knowledge is used as a means to “leapfrog” others – and it is essential to break down the motivators behind these unhealthy behaviours and replace it with cooperation and a team that are more than the sum of their parts.
An example of a tactic I might deploy in such a team would be to introduce knowledge sharing sessions. This could be a series of presentations, where I might lead by presenting a topic that will help the team deliver the project, and then the rest of the team take it in turns to present a topic to the team, either of their own choosing or from a list. This is especially effective in teams that aren’t used to sharing knowledge, as it lets each team member be the “specialist” for an area, and share that specialism with others, while there are also clearly members of the team who are sharing their specialism with them.
I also enjoy mentoring others, to help them with their career growth. Working with developers who are at an earlier stage in their growth is a lot of fun, because they often have a lot of enthusiasm and desire to learn, and a knack of asking questions that make me re-evaluate my knowledge of a subject.
Design patterns are a way for software engineers to label common ways of solving common problems, such that the solution is recognisable by name. Knowledge of design patterns, in the context of when they are applicable, helps engineers to both re-use solutions that are already tried-and-tested, and to create solutions that are easy for others to recognise and understand.
Some common examples of design patterns might include the factory pattern, observer pattern, or singleton pattern. From these names alone, most engineers would implicitly understand both the problem and solution approach. The problem that can come from design patterns is their over-use and abuse; when some engineers first become exposed to these patterns there can be a tendency to try to use these patterns for the sake of it, resulting in adding complexity where none is needed.
Test Driven Development
Test Driven Design (TDD) is the discipline of writing code only to satisfy a failing test, and using these tests to drive the development process. The workflow is to start by writing a test, which should fail, and only then writing code to make the test pass. At this point the code can be refactored, i.e. it can be changed in ways that do not affect its behaviour; as the behaviour does not change the tests should continue to pass. Test Driven Development doesn’t require any particular style of testing, although it is most effective with tests at component level, with tests that test logic rather than infrastructure.
I have successfully introduced TDD to teams who have not used the practice before, by designing and running workshops with the teams based on familiar problems. I enjoy coaching the technique, as a way of changing the way decelopers think about writing software. In order to apply TDD, it is necessary to think first about how an outcome can be verified, and only then to start implementing it. This change in thinking is for me the largest benefit of TDD, although having a healthy suite of tests that validate the behaviour, and also serve to document it, is also very valuable.
Some describe TDD as “training wheels for object-oriented-design”, in that the SOLID design principles and testable code are inextricably linked. The SOLID design principles place a high value on well-organized code, with modules with clear separation of concerns, and inversion of dependencies (especially around infrastructure). These principles lead to code that is easy to test, and writing code by writing tests first will guide developers towards following these principles (whether knowingly or otherwise).
Test Driven Development offers the most value when applied to logic – in its classical form it is far less useful in scenarios such as user interface development, and infrastructure-heavy development. That said, the TDD mindset of first defining acceptance criteria is can always be applied, even if the means of validating those criteria is different. In a more traditional team model, with dedicated “developers” and “testers”, developers were enabled to allow testing to be someone else’s responsibility, and in many programmers the ability to think about outcomes and how to verify them can be under-developed. Learning this skill addresses that defecit.
My personal preference is to combine TDD (writing tests before production code) with BDD (writing tests that describe behaviour in the form of scenarios that map to the business domain). I believe in a fairly loose definition of “unit” in “unit testing”, opting for “unit of behaviour” rather than an implementation detail like a class or method. By applying this style, I can collaboratively write scenarios with a business expert, as a way of making sure we have a shared understanding of the problem being solved.
NCrunch is a continuous test runner for .Net languages. While other test tools interrupt the workflow so that you can run the tests, NCrunch (and others that are on the market now such as ReSharper’s Continuous Test sessions and Visual Studio 2017’s test features) works by running the tests automatically when changes are detected. NCrunch provides a visual indicator at the bottom of the screen that shows whether any tests are currently failing, and allows quick ways of navigating to these tests. Beyond this, every line is marked by red dots (covered by failing tests), green dots (covered by passing tests) and black dots (not covered by any tests), to show for any component exactly what level of coverage is there. It can process this into coverage statistics, which refresh constantly as code is modified.
What I particularly like about NCrunch – especially as a coaching tool when working with developers who are new to TDD – is the workflow it enables. By continuously running tests, the feedback loop is very short – within less than a second there is feedback showing the impact of a change. This also encourages tests at a sensible level, especially where there is a tendency to mix logic and infrastructure and fall back to integration or automation testing, which results in a visibly worse feedback loop.
Behaviour Driven Development
Behaviour Driven Development (BDD) is a style of test-writing, where tests are expressed as scenarios in a way that describe the behaviour of the system in a way that enables close collaboration with business experts with no requirement to understand the implementation of the tests. It can be combined with other test-writing techniques such as TDD, and allows closer integration of the specification of features and their implementation.
A nice team workflow is for BDD features to be written collaboratively, with developers, business analysts and quality analysts all providing input. This helps to leverage all the skills of the team, and to build shared understanding. Many times, a team will find that bugs are caught (and fixed) on paper, simply by finding contradictions in the scenarios without a line of code even being written.
There is sometimes a preconception that because automation tests are often written in a BDD style, BDD is a style most applicable to automation testing, however this is simply not true. Behaviour Driven Development is merely a style for expressing the specification of behaviour, which can be used to validate the implementation in any means available. One thing I like to do is to write BDD tests in such a way that the specifications themselves are re-usable in different tests – only the implementations of the steps change. The scenario does not change, but in an automation test the step is implemented by driving the user interface, while in a component (unit) test the step is implemented by in-memory method invocations (often with mocks or stubs at the boundaries).
SpecFlow is a .Net BDD framework for writing specifications in the Gherkin (Cucumber) syntax and generating .Net classes to execute these specifications. The SpecFlow library parses the .feature files, generating C# code against a test runner of choice (typically NUnit). It offers almost all of the features of the Gherkin specification, such as tables, tags and the like.
I have used SpecFlow for many years, but keep an eye out for alternatives. The trait I would like to see is for a run-time parser of .feature files rather than a compile-time generator; this would mean that .feature files are easier for non-developers to edit outside a code editor, and .feature files are easier to use because there is a more indirect link between the .feature file and any code. One such example is the TickSpec library, which offers a nice way to do this in either C# or F#.
The patterns I adopt when using SpecFlow in a larger scale project are to tightly scope step definitions, opting for a 1:1 mapping of feature/step set rather than widespread re-use. In my experience trying too hard to re-use bindings leads to steps that are written around the implementation rather than the requirements, and un-necessary complexity; tightly scoped steps allow more flexibility and avoid having a large library of similarly named steps. Where re-use is desirable, the approach I take is to write a helper class, and re-use this rather than step bindings. The extreme case I’ve seen is a suite of 3000+ automation-style tests all using a single set of step definitions, at which point breaking this up into manageable chunks had become a very difficult exercise because of the level of coupling between tests.
Load testing is the practice of placing high demands on a system’s capability to measure and improve its ability to withstand such conditions. The goal might be to establish whether there are bottlenecks preventing scaling, to understand the scaling behaviour (e.g. how many users can 1 server support), or to see whether there are any particular problems under heavy load (e.g. deadlocks, timeouts). Performance testing meanwhile is all about measuring the latency experienced by a single consumer, with or without background load. The two are often linked – it is very common for a system’s performance to degrade under load – but can be treated as separate.
Both these types of testing are relatively expensive; they require a deployed environment, and significant time both to implement and to run the tests. They are often neglected completely, which introduces risk to projects, because what can happen is that they are only considered when it is too late and there is an issue that needs resolution. However the risk can be partly mitigated by monitoring, to ensure that trends in the system performance from real-life usage are seen and addressed early. The cost of performance testing as a standalone exercise can then be avoided.
A challenge in implementing these types of testing is in defining what outomes will be measured and how, and in defining what scenarios are to be exercised. If a scenario is too contrived, the result can be meaningless, and mask an actual error. A good example to illustrate this is the “rippled load” scenario – say there is a system divided into separately deployed components, one of which handles user loging, and another handles lobby matchmacking for an online game. A real-world event could be a promotion – a press release, or a special offer of some kind, at an announced time. The user flow here would always be to log in, and then to go to a lobby. While these support automatic scale-out, what will happen is that the login service will respond to its spiked demand, take a while to respond, and fail under inbound load, after which the lobby service will go through the same cycle. Performance tests that treat these as isolated components and ignore the overarching real-world user workflow will mask this issue.
The basic components of these tests are a test client, that generates load and simulates a user interaction scenario, and monitoring, to capture output from the test clients. In order to generate adequate load, these will tend to be cloud-hosted. There are a growing number of off-the-shelf components available, and it is a case of establishing whether any of these can be used rather than resorting to writing something bespoke.
Automation tests rely on the structure of the user interface to find elements to interact with, and one challenge is that every change to the user interface risks breaking these tests. A way round this can be to introduce extra markup solely for the benefit of the automation framework, which has no styling responsibilities, and is managed to be more stable.
In a traditional team set-up, quality analysts or testers and developers form quite separate silos, and automation tests can be sub-siloed to a group of automation testing specialists within the tester group. This results in a set of tests that only a small group of people understand, and only a small group of people pay attention to. Automation testing in this climate becomes a very high-cost low-value exercise. All members of a team, regardless of specialism, should be able to access and understand the automation tests, including understanding the output. Because of the brittle nature of these tests, they will tend to have a higher failure rate than other tests, and the resolution will often be to fix the tests themselves, but these tests can also be the only way of catching some classes of system-level issue.
The goal in designing a suite of automation tests should be for a good return on investment, with low maintenance and implementation costs and high enough coverage. It is not sensible or viable to try to cover the logical behaviour of the system by automation tests – this can be done far more economically with component level tests, given an adequate design of the components involved. The aim for automation tests should be to cover a simple journey through each feature area, testing mainly that the components in the workflow are connected together correctly at runtime. This is especially true for distributed systems, where the boundaries between components can be a weak point. The question that should always be asked before writing an automation test is what the intent of the test is, and whether that could be achieved in a better way.
Smoke testing is the lightweight testing of a deployment to ensure that the deployed instance is operational. This is often done via automation, as an end-to-end flow that establishes quickly whether any component is not deployed or configured correctly. Smoke test scenarios should be designed to hit every part of the infrastructure – for example a smoke test that simply hits a hard-coded HTTP response will tell you whether the HTTP server and network are configured, but nothing about the health of databases or other infrastructure.
Smoke tests can either be run after a deployment to give a one-off health indicator of whether the deployment was successful, or regularly to provide a heartbeat for the system. In conjunction with application level monitoring, a heartbeat can tell you at a glance whether a whole system is offline, or whether there are some partitions that are unavailable.
Azure Service Bus
Amazon Web Services
Amazon Web Services
For scripting in a Windows environment, my preferred choice is PowerShell. I’ve used PowerShell heavily on various projects, for writing build scripts that can be run both locally and on the build server. In some cases, particularly working with Azure, this extends to deployment scripting.
The Azure PowerShell SDK offers all the commands needed to provision and deploy entire environments; on one particular project one of my focus areas was in writing PowerShell scripts to set up and configure all the resources needed from scratch. The result of this was a set of scripts that could be run on an empty Azure subscription, to create and deploy the full solution against an environment’s definition.
Git has become the de-facto standard for source control in recent years, at least partly due to the success of the GitHub platform. Git offers a fully distributed version control system – unlike alternatives like SVN where a central server is significantly different to other nodes. A big advantage of this is that git is fully functional in an offline environment; the only things that can’t be done when offline are pushing or pulling to remote instances, where in other version control systems things like branching may need network access.
I’ve successfully introduced git to several teams, who had previously been using things like SVN or Microsoft’s TFS. While the mental model is a little different, and can take some getting used to, it is easy to learn and be productive. I normally choose to use a GUI for most day-to-day operations – because I’m often working with people who are less familiar, and showing them a visualisation of what is going on is helpful – and I drop back to the command line for troubleshooting or more advanced things.
As a branching/commit strategy, my preference is to have a continuous integration setup, where all developers work against the main branch most of the time. This preference is only valid though if the right circumstances are there to support it; for example the test suite must be good enough that broken builds can be at least detected if not prevented. I often see big problems in teams who try to adopt continuous integration without the pre-requisites in place, which leads to chaos, and in these instances I switch the team to feature branches or a gitflow model as a temporary solution to alleviate pressure on the main branch until an adequate test suite and other preqrequisites can be put in place.
As a build server, my preference is TeamCity; it is simple enough to use, but powerful enough when customisation is needed. I like to write builds in such a way that the build server itself is a dumb runner of scripts that are included with the source code, that can also be run locally; that way there are no surprises, and if anything goes wrong it can be debugged on a developers’ machine with no magic.
I often take on setting up the build systems in teams, and I try to apply a lean approach, building only what is needed, but making sure it is built as soon as it is needed. I’ve seen teams struggle by deferring build pipelines for too long, by which time the solution and build needed for it have become too complicated. By working on the build pipeline alongside regular code, it can grow incrementally. This also discourages people from making design decisions that place a heavy burden on the build system!
Octopus Deploy is a purpose-built deployment management tool, with good integration to build tools like TeamCity. It is good at managing what version of components are deployed to where, with a dashboard for visibility. Many teams start out by deploying via the build tool, but this can become unwieldy – each environment needs its own set of builds and configuration, and deployment takes up build agents for sometimes quite long periods of time.
Octopus deploy works especially well with .Net projects, with tooling such as OctoPack for building a .Net project into a deployable package. The most common use cases – installing Windows services, setting up IIS etc – are catered for by scripts bundled with Octopus Deploy, but by including install scripts inside the Octopus package it is possible to have bespoke deployments relatively easily.
Nuget is the package management system for .Net. It is a way of bundling components into a package format, for sharing either on the public nuget feed or internal feeds for custom packages. It is a valuable tool if following a service-oriented approach, as a means of sharing and versioing the contracts between components, and for sharing cross-cutting libraries.
Nuget packages are also the input format for tools like Octopus Deploy; it is a well-established format with a means of providing both content, with conventions for things like assemblies, and scripts that run at different times in the package install lifecycle.
I’ve worked with various forms of SQL databases on almost all projects in some form or another. While NoSQL stores have their place, I think there is very definitely still a place for SQL in many solutions. While NoSQL stores often have quite “spiky” capability sets – they are very good at some things, but cannot do others – SQL is a very solid all-rounder, and it is so well-known that any problems have probably been solved before.
SQL is the canonical relational database, built on tables, rows and columns, with tables joined by primary/foreign key. Modelling domains into tables is often done by applying the Normalisation approach, aiming to represent each piece of data once each in joined tables rather than having duplicated data. The reverse technique, Denormalisation, can sometimes be useful in cases where the performance of a join would be unfavourable.
I’ve mostly worked with Microsoft’s SQL server, with various editions of the main product and the Azure offering. All are based on the standardised language, but there can be nuances – Azure’s SQL in particular omits some features. I’ve also worked with Amazon’s RDS implementation, and with things like SQLite, a minimised version that can run cross-platform on phone hardware for application-level local storage.
I enjoy working with the SQL language; it is a very rich and declarative language, very well suited to working with set based logic, and very mature. Sometimes on projects it can be faster to abstract away SQL by using ORM tools, but I quite like using light-weight libraries such as Dapper combined with hand-written queries.
Entity Framework is Microsoft’s data access platform; it offers a powerful way of working with data in quite a well-abstracted way, leveraging the power of Linq. I’ve worked with all versions from the first release, with the database-first, model-first and code-first paradigms.
What I like about Entity Framework is how quickly functionality can be built out. The performance is reasonable, but normally a hand-written query can be faster. I find it effective to use Entity Framework, and replace specific components with hand-written queries where there is a need (normally for performance).
RavenDb is a document store written in .Net. I’ve used versions of RavenDb on several projects, since its early releases. RavenDb is an example of a database that has “right ways” and “wrong ways to work with it, where it can be very nice to work with in its sweet spots but cause problems if it is shoe-horned in to a problem it’s not designed to solve.
The key to working with RavenDb is to respect its paradigms. Treating transaction boundaries as documents – rather than trying to build a conventional domain model around entities – and leveraging the powerful indexing capabilities are a good start in getting the best from it.
A nice aspect of RavenDb is the developer experience, with things like in-memory test implementations for integration testing of indexes available out-of-the-box.
I’ve worked with RavenDb as both a primary store, as the main store for business data, and as a secondary store of things like short-lived workflow data.
ElasticSearch is a document store with powerful search capabilities provided by indexing the documents, normally in Lucene. The documents being written to the database are analysed at the point of write, so that searching later is against these highly optimised indexes rather than raw data.
I’ve worked with ElasticSearch several times as a logging store – in conjunction with things like Kibana. This allows log data to be collected, aggregated, and searched quite effectively, even with quite high rates of data collection.
I’ve also worked with ElasticSearch as the backing store for a data search product, where data was ingested from a write-friendly SQL data store into the read-friendly ElasticSearch store. The data structures for these split data stores can then be optimised for the task at hand, giving the best of both. The trade-off in a separate store for reads is that there is a data synchronisation process needed, which can add complexity and cause the data in the read store to be “eventually consistent” – i.e. there can be a delay before writes are shown.
Graph databases are very powerful for modelling relationships between data, and traversing those relationships. Whereas a relational database operates with tables and keys, the relationships in a graph are a first-class concern. Neo4J is an example of a graph database that I’ve used in side projects, but not yet used in a commercial project.
An advantage of graph databases over relational stores is that the speed of a graph query is related to the number of connections in to/out of each individual node, whereas the performance of a table join can be influenced by the total size of data in the table, and so the performance for a graph query against a large data set that goes through many relationships can be much faster and stay more stable with size.
Modelling domains with a graph database can be a more natural fit that modelling tables, especially as both the nodes (objects) and edges (connections) can have properties. Connections are also named, and directed, which can lead to a more intuitive and richer data model.
Key-value storage is a highly read-optimised way of dealing with data, so long as that data can be associated with a single key. Document databases are often a specialism of key-value stores, where the value is a richer document rather than a simple value. The trade-off is that aggregation, or operating on more than one value at a time, are significantly more expensive. An example of a key-value store would be something like Redis.
Key-value stores can make effective caches or view model stores, where the value would be expensive to calculate on-the-fly, but can be pre-calculated and stored against a single key for quick retrieval.
I’ve used key-value stores mostly for storing things like user profile data in Azure Table Storage, where there is no requirement to aggregate the data, the data is slow-changing, and the data is always accessed by key (the user’s identity).