May 31, 2016
I wrote earlier about easing integration when providing reusable software assets. In this post, will elaborate on specific techniques for driving platform adoption & their rationale. Recognize that most developers who will evaluate your platform are trying to solve a specific business / functional problem. They want to quickly ascertain if what you are providing is a good fit or not. Instead of convincing them, help them arrive at a decision. Fast. How exactly do you do that? Here are a few ideas:
- Provide details on the kinds of use cases your platform is designed to address. Equally important is to be transparent about the use cases that you don’t support. Not now and never ever will.
- Create developer accelerators – e.g. a Maven Project Archetype or a sample project to try out common functionality
- Identify areas where developers can extend the platform functionality – how will they supply or override new behavior? How will you make it possible to inject, easy to test, and safe to execute? There are lots of techniques that you can use but first you have to decide to what extent you want to allow this in the first place.
- Make your platform available in “localhost” mode – i.e. conducive for use with the IDE toolset. This is more challenging than what it sounds – e.g. if your platform isn’t modular, making it work in local mode will be very challenging. Ditto if your platform relies on external services / connectivity / data stores, etc. that aren’t easily replaceable with in-memory / mock equivalents.
- Allow developers to discover your platform via multiple learning paths. Some might want to explore using a series of Kata lessons that tackle increasingly complex use cases. Others might be looking for answers to a specific problem. You need a user guide, code kata, examples, and more importantly, you need to make them easy to access.
- Identify which areas of the platform adoption curve are the most time consuming and figure out how to reduce if not eliminate them entirely. For instance – does your platform require an elaborate onboarding process? Are there steps that can be deferred till production deployment?
May 29, 2016
Managed platforms are a very effective and pragmatic way to drive systematic reuse across an organization. Managed platforms can provide a number of benefits ranging from simplified developer experience, out of the box productivity components and tooling, and most importantly, a whole host of non-functional concerns being addressed in an integrated fashion. Good managed platforms exhibit some common traits. They:
- Solve a specific problem really, really well
- Are easy to signup, develop in, and use via a developer SDK
- Provide a number of integrated components that address specific pain points
- Are extensible and provide clear and safe injection points via a public, published, well maintained API
- Free the developer from having to procure hardware or orchestrate deployment activities
- Make it easy to report bugs, fixes, and contribute enhancements
The best part of managed platforms? They can dramatically alter the productivity curve for your development teams. Every developer doesn’t have to worry about high availability, horizontal scaling, capacity management, public APIs, version management, backward compatibility, and ongoing care and feeding of core reusable components. The platform provides value and more specifically peace of mind via powerful abstractions.
This isn’t an exhaustive list but given the general push towards cloud based architectures, good platforms will give your teams much more than just reuse! I will elaborate on each of the above traits in follow up posts.
May 28, 2016
Although starting from scratch is simpler when building reusable assets, reality is that you are probably maintaining one or more legacy applications. Refactoring existing legacy assets has several benefits for the team. Here are a few:
- The refactoring effort will make you more knowledgeable about what you already own
- Will help you utilize these assets for making your systematic reuse strategy successful
- Saves valuable time and effort with upfront domain analysis (of course this is assuming what you own is relevant to your present domain)
- Make your legacy system less intimidating and more transparent.
- Provides the opportunity to iteratively make the legacy assets consistent with your new code
If you cannot readily identify which legacy module or process is reusable you have two places to get help – your customer and your internal subject matter experts. Your customer can help you with clarifying the role of a legacy process. Likewise, your team probably has members who understand the legacy system and have deep knowledge of the domain. Both of them can guide your refactoring efforts.
The act of examining a legacy module or process also has several benefits. You can understand the asset’s place in the overall system. The existing quality of documentation around this asset and usage patterns can be understood as well. Now, you can make an informed opinion of the current state of what you have and how you want to change it. Before making any changes though it helps to consider the next few moves ahead of you. Ask a few questions:
- Is the capability only available in the legacy application and not in any other system that you active maintain and develop?
- Is it available only to a particular user group, channel, or geography?
- Is the capability critical to your business sponsor or customer? If so, are they happy with the existing behavior?
- How is the capability consumed currently? Is it invoked as a service or via a batch process?
- How decoupled is the legacy capability from other modules in the application?
These questions will help you get clarity on the role of the legacy asset, its place in the overall application, and a high level sense of the effort involved in refactoring it to suit your requirements
January 6, 2015
Tip #32 – Fail the build when there is a test failure
Systematic reuse needs automated tests – lots of them. Ensure that your continuous builds don’t keep running when they encounter a failing test – this makes mistakes visible faster and easier to fix. If you are using Maven, you can use the surefire plugin configuration as shown below:
July 6, 2014
Here are a few quick tips to examine your existing code to identify reuse candidates:
- Introduce Factory or Builder instead of repetitive boiler-plate code when constructing key objects. Several benefits: makes it easy to refactor and evolve how underlying objects are stitched together, makes it easy to write more intention revealing code, and very relevant / convenient when writing automated tests
- Clarify functional behavior and tease out non-functional logic – look at how functional logic is implemented across your codebase. Do multiple projects share a common set of domain assets – objects, rules, services, and the like? if not, look for areas where functional logic is tightly bound to non-functional aspects like fine-grained metrics capture, exception handling, retry / timeout handling, etc.
- Public API that needs to be accessed across platforms / devices are strong reuse candidates – for instance, are there functional APIs that don’t have a remote interface (e.g. a plain Java implementation without an appropriate REST-ful web service resource)? Do you need functionality to be made available across multiple devices with varying User Interface semantics? If so, look to carve the shared logic out into a service that can be accessed from multiple devices / integration points
July 6, 2014
Resisting the temptation to implement a story is very hard for a dev team – it is all too easy to get carried away in introducing a new idea as a reusable asset. However, these are the moments where you must pause and ask yourself a few basic questions before rushing to write code:
- Is this a one-off requirement or part of a recurring theme? Remember – when in doubt, don’t plan for reuse but continuously align and refactor your codebase. Use before reuse!
- Is there a confirmed consumer of this asset beyond the immediate project? If not, keep it in the originating project and promote it for reuse when you find another team or project that needs the functionality
- What are the likely variations that aren’t captured in the existing implementation? are they all really required for day 1, day N ? Don’t implement anything unless there is a business mandate to do so. It is very easy to add code wishing someone reuses it rather than refactoring a known good piece of functionality
- Do you have the bandwidth to develop unit tests, capture the right domain abstractions, and write developer facing quick start documentation? If not, resist the urge to prematurely introduce new code that ends up being technical debt
November 10, 2013
In an earlier post, I listed reasons why automated tests are foundational for reuse. In this post, want to provide some approaches that will ease automated testing of your components.
- Mock API interactions when using external dependencies. Mocking will reduce runtime dependencies and make your unit tests faster and more robust. Use JUnit with Mockito – mockito has excellent support for a variety of mocking use cases.
- If an external dependency is required from multiple classes, you can define an Adapter that will wrap the external API via an interface. The interface can then be mocked or stubbed and will provide an abstraction layer for your classes. Word of caution: abstractions are leaky and resist the need to wrap every single API provided by the external dependency.
- Use in-memory databases and provide a consistent API for your tests. A common class could initialize and clean up the in-memory db and can be leveraged from tests. Alternatively, it can be provided as an abstract class that your tests can extend. Take the opportunity to standardize location, naming, and directory structure of test resources – if you are using maven for instance, the db related data files can be placed under src/test/resources/db/<db-name>. Finally, this is very useful in ensuring that the database-bound code is indeed testable – forcing the in-memory db test will make technical debt apparent.
- Use db-deploy or some automated database deployment tooling to define and populate databases from tests – these can enable developers to define and execute tests without sharing / corrupting each other’s data. It will also make your database deployment repeatable and well tested eliminating a key deployment risk.
- Provide a common API for routinely used tasks for developers – e.g. APIs that can create test data in in-house / proprietary formats, parse, and populate appropriate data structures will be useful.
- Use JUnit Rule extensions for having a common API for developers – provide a custom rule that will manage the lifecycle of a legacy component or a API that is difficult to use – these are all opportunities to both facilitate testing and add value via reuse.
November 2, 2013
You can wait for that dream initiative or project to build a whole new set of reusable components that will magically make your teams more productive. The only issue is – it is highly likely that it will be just that – a dream. Instead of planning for systematic reuse, start executing on it by taking a few simple steps. Ask yourself the following questions:
1. Are you capitalizing on identifying and sharing common components with your department / team?
2. Is every project encouraged to continuously refactor and harmonize classes for reducing redundancies? If not, why not?
3. Do you have code that caters to common infrastructural concerns – logging, exception management, alerting, monitoring, metrics.? If yes, is their reuse mandated via common framework hooks that your developers are already using? If not, what is preventing adoption of these concerns into your development stack? Ask your developers and listen to their concerns – you will need to unearth and attack the root causes behind reuse barriers.
4. Do you utilize ad-hoc, informal pairing and code review sessions to identify and harmonize similar / duplicate / redundant classes? If you review code the first time before a project go-live, odds are you either will regret missed opportunities or bemoan the lack of time within your development cycle for making improvements. Key is to intervene early and often and front load your investments for systematic reuse
5. How do you ensure reafactoring to reuse opportunities are tracked? do you create improvement tickets and action them on a best-effort basis or are they managed as part of the product backlog of things that have to get done? If its the former, it will be difficult to make much progress. Creating and tracking tickets will will provide visibility – however, for you to make tangible progress in acting on them you need to partner with developers and development managers to action work on an ongoing basis.
These are just example questions to help you get your journey started and it should be abundantly clear that discipline and continuous alignment is key. If you don’t do anything else, just force your team to converge on a common implementation on key functionality. You will be surprised what discipline can deliver.
September 9, 2013
What is the one key aspect of making your APIs reusable? Simply put – reducing the barriers to adoption. It matters because your target audience – architects, developers, and project managers – are all trying to deliver value to their clients. Iteration after iteration. Here are specific strategies to reduce the adoption barriers:
- Make the public API as intention revealing as possible. Looking at the public interface it must be very clear and unambiguous what the purpose and scope is of the reusable capability.
- Provide Builder or abstract factory APIs to stitch together complex object graphs in a simple fashion. Make it very easy to wire up the default behaviour and provide hooks to replace specific bits of behaviour that is varying. For example – if saving to a file is a default, provide alternative implementations or an interface hook for your client to inject a custom one.
- Make it easy to integrate the capability with other complimentary APIs. These could be in-house, open-source, or vendor APIs – key is – if they are likely to co-exist in a solution, make it simple to wire them together. If your developers use Spring for dependency injection, make your components easy to work with when it. Ditto for in-house frameworks that your teams use heavily.
- For reusable assets that perform orchestration logic provide hooks for injecting custom logic before / after your API behaviour This will be useful for initializing / cleanup logic – specially with legacy components that may need a bespoke set of steps to start them or free up resources on shutdown.
- Provide Null and/or mocking friendly implementations for testability. Your clients need to test their code via automated tests and so do you as the provider – both these needs are met via testable providers. Make it easy for developers to use them with JUnit and make it hard for them to bypass the public APIs.
August 31, 2013
Tip #31 – Inject Common Reusable Capabilities via JUnit Rules
JUnit has an extremely useful extension mechanism – Rules. The @Rule annotation can help provide additional capabilities to your test methods. For example, ContiPerf provides annotations for performance testing. Similarly, you can provide reusable framework hooks for developers to use alongside their test methods.
Some examples where this can be applied – capturing test execution metrics and publishing to a API for offline trending/analysis or setting up plumbing components to facilitate in-memory db testing via H2db, or data folders, etc.
Implementing a JUnit rule is quite straight forward – here’s an article from David Gassner that provides a sample rule implementation.