Flexible, maintainable, and reliable software creating strategic advantages.
Write It or Buy It
The first important question we have to answer as a software engineering leader is whether we should be creating this application ourselves, looking at commodity software, or outsourcing. If you just squirmed a bit, you might be thinking this is a question for someone else. Maybe a product manager or someone higher up the decision-making ladder. I don’t think so. We are responsible for maximizing the value of our teams. One way to do that is through a relentless focus on creating a strategic advantage for our organizations.
The decision to write it in-house is fairly straight-forward once we get over any personal entrepreneurial biases. If the software adds value within the core domain of the business, then it is strategic. If it is strategic, it should absolutely be developed in-house. If it only supports the core domain, then we need to consider other options. Supporting domains need solving too, so we need to weigh the advantages and disadvantages inherent in the various ways we can purchase software solutions for them.
Expressing Business Value
Whether we are creating a consumer product or complimentary software to empower our business internally, the code should speak the same language as the folks driving the value proposition. From the top-level architecture to events and interfaces, a business person listening to the code being discussed should be able to roughly relate it to the value being created. This requires engineering teams to understand the product and customers so they can use their language consistently, accurately, and within the correct contexts.
Domain-Driven Design (DDD) provides a very effective framework for creating what it calls, “The Ubiquitous Language.” By collaborating closely with the business to develop this language, the structure, nouns, and verbs of the code speak directly to problems being solved, rather than how they are being solved. This helps us focus on value creation, prioritize, and even increases maintainability.
Maximizing Value
So far, we’ve decided that we are only going to homebrew software that enhances competitive advantage by focusing on solutions that directly serve the business strategy. We are then going to work closely with our product management to develop a ubiquitous language to express the relationship between our business and the value generated by the software. Now, we need to get the most from our efforts.
To maximize value creation we need to have a pretty good idea of the following characteristics of the product:
- What is the lifespan?
- Will it need to evolve over time, even after being deployed to production?
- How often will new features need to be deployed?
- What features are likely to change the most frequently?
- What are the key value-creating features?
- What are some features of the market we are targeting?
- Who will be using this software?
The answers to these questions and others will help shape the processes we use to develop the software. Decisions about which flavor of Agile, which test automation approach we will use, and fundamental architectural decisions are all levers we can pull to tune our practices to fit the product. When we can align our practices, the fundamentals of culture with the product, we set ourselves up for great success!
Three or More Ways
Within the software community, there are many passionate debates about process and technology. Sometimes these debates lean towards dogmatism or pragmatism. Often these are seen as mutually exclusive modes. I don’t buy into the pragmatism vs. dogmatism debate. Pragmatism can be its own dogma. And sometimes, dogmatic tenants are pragmatic. When it comes to building valuable products as efficiently and effectively as possible, context is king.
Any given practice is rarely, if ever, a best practice outside specific contexts. Understanding fundamentals like time horizons, rate of change, cost of defect resolution, costs of design, and value potential is necessary to select practices that will empower you with the best chance to meet your goals given your environment.
There is no one true way. There at least three, if not many more.
Start Using Flexible Defaults
If we accept that there are no best practices and that dogmas are overly-limiting, then what? Is our only option nihilistic engineering? I don’t think so, at all. What we can do is choose flexible defaults. In other words, we start with a given set of practices that don’t paint us into corners that are hard to escape.
It might be useful to start by thinking of practices that are difficult to back out from. Over-reliance on manual or other end-to-end testing is difficult to pivot away from. Going from truly micro-sized services to right-sized services doesn’t need to be difficult. Still, it does mean we spent significant time and complexity on something unnecessary early in development. If those micro-services are tightly coupled, then it will be a big problem. Moving from no design or over-design to just-enough design are other difficult transitions.
In general, I default to Scrum, OOP, classical TDD, Domain-Driven Design, reactive applications, and mono-repo monoliths. Of all of these, the last might be the most controversial. In short, the monolith is the best background to properly examine a system for right-sized services as the need emerges. One or some combination of the following must be true to justify a service:
- Need for independent rates of change.
- The product has been split into more than one with different product owners and development teams.
- Integration, delivery, or deployment exceeds timeliness thresholds.
- Need for independent scalability.
- Particular modules, functions, or classes having a disproportionate effect on the load.
- The need for immediate data consistency is isolated to clear groups of entities.
If we built the application to be reactive from the beginning using DDD, our refactoring for a different deployment scheme would be fairly trivial and highly targeted. On the other hand, if we default to a deductive or reductive approach to defining services too early, we miss the power of empirical analysis to maximize work not done.
I’ll dig into this and the other flexible defaults in future posts.
Serve the Customer
Ultimately, the processes we use to create our software should serve our customers best. The nature of the product, the market, and the development team influence what will work best, given specific goals.
