Bottleneck #01: Tech Debt

Mike's Notes

An excellent article on technical debt was copied from Martin Fowler's website. I discovered this by reading a presentation given to a Dunedin meetup group.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library > Authors > Martin Fowler
  • Home > Handbook > 

Last Updated

09/05/2025

Bottleneck #01: Tech Debt

By: Tim Cochran & Carl Nygard
martinfowler.com: 09/03/2025

Tim Cochran is a Technical Director for the US East Market at Thoughtworks. Tim has over 19 years of experience leading work across start-ups and large enterprises in various domains such as retail, financial services, and government. He advises organizations on technology strategy and making the right technology investments to enable digital transformation goals. He is a vocal advocate for the developer experience and passionate about using data-driven approaches to improve it.

Carl Nygard is a Technical Principal at Thoughtworks. Carl has over 20 years of experience leading teams from start-ups to large enterprises building solutions for GIS/remote sensing, supply chain, real-time controls, online education, retail, and government. He works with organizations to develop technology strategy to achieve business outcomes through optimized software delivery practices.

Accumulation of tech debt; experiments and shortcuts are core components

Part of a series on bottlenecks

In its early days, a startup searches for a good product-market fit. When it finds one it looks to grow rapidly, a phase known as a scaleup. At this time it's growing rapidly along many dimensions: revenues, customer, headcount. At Thoughtworks, we've worked with many such scaleups, and our work has focused on how to help them overcome various bottlenecks that impede this growth.

As we've done this work, we've noticed common bottlenecks, and learned approaches to deal with them. This article is the first in a series that examines these bottlenecks. In each article we'll look at how startups get into the bottleneck, usually through doing the right things that are needed early in a startup's life, but are no longer right as growth changes the context for ways of working. We'll highlight key signs that the startup is approaching or stuck in the bottleneck. We'll then talk about how to break through the bottleneck, describing the changes we've seen that allow scaleups to reach their proper potential.

We start this series by looking at technical debt: how the tools and practices that facilitate rapid experimentation of the product/market fit need to change once growth kicks in.

How did you get into the bottleneck?

The most common scaling bottleneck we encounter is technical debt — startups regularly state that tech debt is their main impediment to growth. The term “tech debt” tends to be used as a catch-all term, generally indicating that the technical platform and stack needs improvement. They’ve seen feature development slow down, quality issues, or engineering frustration. The startup team attributes it to technical debt incurred due to a lack of technical investment during their growth phase. An analysis is required to figure out the type and scale of the tech debt. It could be that the code quality is bad, an older language or framework is used, or the deployment and operation of the product isn’t fully automated. The solution strategy might be slight changes to the teams’ process or starting an initiative to rebuild parts of the application.

It’s important to say that prudent technical debt is healthy and desired, especially in the initial phases of a startup’s journey. Startups should trade technical aspects such as quality or robustness for product delivery speed. This will get the startup to its first goal – a viable business model, a proven product and customers that love the product. But as the company looks to scale up, we have to address the shortcuts taken, or it will very quickly affect the business.

Let’s examine a couple of examples we’ve encountered.

Company A – A startup has built an MVP that has shown enough evidence (user traffic, user sentiment, revenue) for investors and secured the next round of funding. Like most MVPs, it was built to generate user feedback rather than high-quality technical architecture. After the funding, instead of rebuilding that pilot, they build upon it, keeping the traction by focusing on features. This may not be an immediate problem since the startup has a small senior team that knows the sharp edges and can put in bandaid solutions to keep the company afloat.

The issues start to arise when the team continues to focus on feature development and the debt isn’t getting paid down. Over time, the low-quality MVP becomes core components, with no clear path to improve or replace them. There is friction to learn, work, and support the code. It becomes increasingly difficult to expand the team or the feature set effectively. The engineering leaders are also very nervous about the attrition of the original engineers and losing the knowledge they have.

Eventually, the lack of technical investment comes to a head. The team becomes paralyzed, measured in lower velocity and team frustration. The startup has to rebuild significantly, meaning feature development has to slow down, allowing competitors to catch up.

Company B – The company was founded by ex-engineers and they wanted to do everything “right.” It was built to scale out of the box. They used the latest libraries and programming languages. It has a finely grained architecture, allowing each part of the application to be implemented with different technologies, each optimized to scale perfectly. As a result, it will easily be able to handle hyper growth when the company gets there.

The issue with this example is that it took a long time to create, feature development was slow, and many engineers spent time working on the platform rather than the product. It was also hard to experiment — the finely grained architecture meant ideas that didn’t fit into an existing service architecture were challenging to do. The company didn’t realize the value of the highly scalable architecture because it was not able to find a product-market fit to reach that scale of customer base.

These are two extreme examples, based on an amalgamation of various clients with whom the startup teams at Thoughtworks have worked. Company A got itself into a technical debt bottleneck that paralyzed the company. Company B over-engineered a solution that slowed down development and crippled its ability to pivot quickly as it learnt more.

The theme with both is an inability to find the right balance of technical investment vs. product delivery. Ideally we want to leverage the use of prudent technical debt to power rapid feature development and experimentation. When the ideas are found to be valuable, we should pay down that technical debt. While this is very easily stated, it can be a challenge to put into practice.

To explore how to create the right balance, we are going to examine the different types of technical debt:

Typical types of debt:

Technical debt is an ambiguous term, often regarded as purely code-related. For this discussion, we’re going to use technical debt to mean any technical shortcut, where we’re trading long-term investment into a technical platform for short-term feature development.

Code quality

Code that is brittle, hard to test, hard to understand, or poorly documented will make all development and maintenance tasks slower and will degrade the “enjoyment” of writing code while demotivating engineers. Another example is a domain model and associated data model that doesn’t fit the current business model, resulting in workarounds.

Testing

A lack of unit, integration, or E2E tests, or the wrong distribution (see test pyramid). The developer can’t quickly get confidence that their code will not break existing functionality and dependencies. This leads to developers batching changes and a reduction of deployment frequency. Larger increments are harder to test and will often result in more bugs.

Coupling

Between modules (often happens in a monolith), teams potentially block each other, thus reducing the deployment frequency and increasing lead time for changes. One solution is to pull out services into microservices, which comes with it’s own complexity — there can be more straightforward ways of setting clear boundaries within the monolith.

Unused or low value features

Not typically thought of as technical debt, but one of the symptoms of tech debt is code that is hard to work with. More features creates more conditions, more edge cases that developers have to design around. This erodes the delivery speed. A startup is experimenting. We should always make sure to go back and re-evaluate if the experiment (the feature) is working, and if not, delete it. Emotionally, it can be very difficult for teams to make a judgment call, but it becomes much easier when you have objective data quantifying the feature value.

Out of date libraries or frameworks

The team will be unable to take advantage of new improvements and remain vulnerable to security problems. It will result in a skills problem, slowing down the onboarding of new hires and frustrating current developers who are forced to work with older versions. Additionally, those legacy frameworks tend to limit further upgrades and innovation.

Tooling

Sub-optimum third-party products or tools that require a lot of maintenance. The landscape is ever-changing, and more efficient tooling may have entered the market. Developers also naturally want to work with the most efficient tools. The balance between buying vs. building is complex and needs reassessment with the remaining debt in consideration.

Reliability and performance engineering problems

This can affect the customer experience and the ability to scale. We have to be careful, as we have seen wasted effort in premature optimization when scaling for a hypothetical future situation. It’s better to have a product proven to be valuable with users than an unproven product that can scale. We’ll describe this in more detail in the piece on “Scaling Bottleneck: Built without reliability and observability in mind”.

Manual processes

Part of the product delivery workflow isn’t automated. This could be steps in the developer workflow or things related to managing the production system. A warning: this can also go the other way when you spend a lot of time automating something that is not used enough to be worth the investment.

Automated deployments

Early-stage startups can get away with a simple setup, but this should be addressed very soon—small incremental deployments power experimental software delivery. Use the four key metrics as your guidepost. You should be able to deploy at will, usually at least once a day.

Knowledge sharing

Lack of useful information is a form of technical debt. It makes it difficult for new employees and dependent teams to get up to speed. As standard practice, development teams should produce concisely written technical documentation, API Specifications, and architectural decision records. It should also be discoverable via a developer portal or search engine. An anti-pattern is no moderation and deprecation process to ensure quality.

Is that really technical debt or functionality?

Startups often tell us about being swamped with technical debt, but under examination they’re really referring to the limited functionality of the technical platform, which needs its own proper treatment with planning, requirement gathering, and dedicated resources.

For example, Thoughtworks' startup teams often work with clients on automating customer onboarding. They might have a single-tenant solution with little automation. This starts off well enough — the developers can manually set up the accounts and track the differences between installs. But, as you add more clients, it becomes too time-consuming for the developers. So the startup might hire dedicated operations staff to set up the customer accounts. As the user base and functionality grows, it becomes increasingly difficult to manage the different installs — customer onboarding time increases, and quality problems increase. At this point automating the deployment and configuration or moving to a multi-tenant setup will directly impact KPIs — this is functionality.

Other forms of technical debt are harder to spot and harder to point to a direct impact, such as code that is difficult to work with or short repeated manual processes. The best way to identify them is with feedback from the teams that experience them day-to-day. A team’s continuous improvement process can handle it and shouldn’t require a dedicated initiative to fix it.

Signs you are approaching a scaling bottleneck

Value lead time

Looking at the end-to-end process of providing value to users and how it trends over time will highlight friction between technical debt and other problems.

Impact to end user

Latency in the systems, customer onboarding time, and quality issues will impact the customer — a technical shortcut could be the root cause.

Engineering satisfaction

There are multiple products in your system: one the users experience and the other being what the employees and developers experience. Listening to your developers’ complaints will bring up fundamental issues in the technical platform, enabling prioritization of what will impact them the most.

Ability to onboard new developers

Looking at the onboarding process and the satisfaction of new developers can surface problems, which long-term employees have built a habit of avoiding.

Degradation in Non-Functional measures

Run-time infrastructure costs, performance and availability can all be indirect indicators of excessive technical debt impacting business outcomes.

If you see any of these signs already, your product roadmap can reveal where to target investment in improvements. The biggest negative effect of your technical debt is going to be caused by the parts of your platform your future product requires.

How do you get out of the bottleneck?

The approach that teams are taking to technical debt should come from its technical strategy, set by its leaders. It should be intentional, clear, and re-evaluated over time. Unfortunately, we often see teams working off historical directions, creating future problems without realizing it. For a company in this circumstance, a few opportunities commonly trigger when to re-evaluate their current strategy:

  • New funding means more features and more resources — this will compound current problems. Addressing current technical debt should be part of the funding plan.
  • New product direction can invalidate previous assumptions and put stress on new parts of the systems.
  • A good governance process involves reevaluating the state of the technology on a regular cadence.
  • New opinions can help avoid “boiling frog” problems. Outside help, team rotations and new employees will bring a fresh perspective.

The slippery slope

How did you end up with a lot of technical debt? It can be very hard to pinpoint. Typically it isn’t due to just one event or decision, but rather a series of decisions and trade-offs made under pressure.

Ironically, in retrospect, if one considers each decision at the point in time at which it was made, based on what was known at the time, it is unlikely to be considered a mistake. However, one concession leads to another and so on, until you have a serious problem with quality. There is commonly a tipping point at which resolving the tech debt takes more time than developing incremental value.

It’s hard to recover and the situation tends to snowball. It is natural for developers to use the current state as an indicator of what is acceptable. In these conditions, developing the new features will result in even more debt. This is the slippery slope, a vicious cycle that unfortunately leads to a cliff as the effort to implement the next feature increases non-linearly.

Set a quality bar

Many organizations find it beneficial to have a set of standards and practices to which the company is committed that guide technical evolution. Keep in mind that some technical practices are quite difficult to achieve, for example continuous delivery; deploying regularly without affecting users is technically challenging. Teams often have initial problems, and in response leadership may deprioritize the practice. Instead we recommend the opposite, do it more often and your teams will master the practices and form strong habits. When the tough time comes, rather than dropping the practice, use the feedback to guide future investment in team capability.

Blast Radius

We accept that taking shortcuts is a necessary part of scaling the business. How do we limit the blast radius, knowing that these shortcuts will need to be resolved, or even totally rebuilt? Clearly, we need a strategy that limits the impact to the business. One way is to decouple teams and systems, which allows a team to introduce tech debt that is isolated and won’t necessarily snowball as described above.

High quality literature about decoupling is plentiful, so we won’t attempt to explain here. We recommend focusing attention on microservices and domain driven design techniques. However, be careful doing too much too early, decoupling adds latency and complexity to your systems, and choosing poor domain boundaries between teams can add communication friction. We will be writing about anti-patterns related to overcomplicated distributed architectures in future articles.

Product and Engineering Collaboration

If trade off conversations aren’t balanced between business strategy, product and engineering, technical quality most commonly degrades first, and as a result product quality eventually suffers as well. When you look for the root cause of this bottleneck, it nearly always comes down to the balance within the company between business, product and engineering goals. Lack of collaboration typically leads to short sighted decisions made in a vacuum. This can go both ways, cutting corners in critical areas or gold plating something that isn’t valuable are equally likely.

  • The business strategy at any point in time should be clear and transparent.
  • We empower team leaders to make decisions which benefit the business.
  • Product and Engineering should have an equal footing, trust in each other, and be willing to make trade off decisions based on long and short term impact to the business.
  • Decisions are made with data – e.g. the current state of the technical platform, estimates, analysis of expected value and KPI improvement, user research, A/B test results.
  • Decisions are revisited when data is refined or new learnings are discovered.

A tech strategy to limit technical debt impact

When thinking of strategies for a startup, and how it scales, we like to use a four-phase model to understand the different stages of a startup's development.

===================

Phase 1

Experimenting

  • Prototypes - semi-functional software to demonstrate product, moving to functional with increasing interest

Phase 2

Getting Traction

  • Ecosystem decisions - cloud vendor, language choices, service integration style
  • Replace prototype software for core systems
  • Setup initial foundations - experimentation, CI/CD, API, observability, analytics
  • Establish the broad domains, set initial soft boundaries (in code)

Phase 3

(Hyper) Growth

  • Create decoupled product teams managing their own services
  • Establish SLAs and quality bar, linked to signals around customer experience of product
  • Establish platform teams focused on the effectiveness of product teams

Phase 4

Optimizing

  • Reassess SLA and quality bar focused on long term productivity and maintenance
  • Audit state of technical platform, sponsor initiatives in product teams and create temporary tiger teams to fix biggest technical debt
  • Rebuild or buy capabilities for improved efficiency
  • Train teams on good technical quality practices

How do you address the tech debt

It starts with transparent information sharing how the business is doing, the current product direction, metrics on the current scaling capacity, what customers are saying about the product and what customer support and ops are seeing. This information will allow technologists to make informed decisions. Sharing the data of the current challenge helps technologists to know why problems are being addressed and measure their success.

There should be clear end-to-end ownership of all products and their related systems. As teams grow and take responsibility for their respective areas, there is often no clear ownership for an end-to-end journey, which leaves technical gaps that often become filled with technical debt. As teams grow and take on new duties, it becomes increasingly difficult to find an owner for older code. Furthermore, without ownership, teams are less incentivized to fix problems.

We have to empower teams to fix problems — resolving technical debt should be part of the natural flow of product development. Engineers and product managers need to negotiate the healthy balance between tech debt vs. functionality with the right pragmatic mentality. It’s part of a product team’s job to maintain and sustain technically healthy products, not something done as an after-thought. There should be an agreed process to tackle and monitor technical debt continually. This requires hard trade-offs among engineering and product leaders to keep a stable balance.

Designing your team topology the right way can also be a factor. For example, suppose we continually see technical debt created in certain areas. In that case, it might indicate that the team design is wrong, and there might be a platform or business capability that needs strong ownership and attention.

Some metrics are powerful — for example, scanning for common mistakes or measuring build and deployment times. The engineering organization should provide self-service tooling into which teams can quickly integrate their systems. Metrics should be used as guides for the team to make decisions about tech-debt rather than for managers to monitor or incentivize. Experienced developers provide value by interpreting the available data and grounding their intution in fact-based qualitative information.

While we believe in autonomous teams, too much autonomy can be a problem and can result in a chaotic technical landscape. There should be lightweight checks and balances such as automated checks or architectural peer review, which can help enforce policies and aid developers.

How your organization chooses to address its tech debt depends on your context. One common theme we have seen across many organizations is the desire to “just do something,” often resulting in a band-aid which soon creates its own set of frictions. Instead, we’ve found that taking an iterative approach and letting the metrics combined with current development activity guide the investment in resolving tech debt results in better outcomes.

Summary

  • Taking on prudent technical debt is necessary and healthy for early stage startups.
  • Look for warning signs (value lead time, or engineering satisfaction) that your technical debt is going to constrain your business.
  • Have a clear technical quality bar, and enable teams to stick to it.
  • Create a technical debt process, clear ownership and empowered teams that have access to transparent information to make informed decisions.
  • Necessary technical platform improvements may be masquerading as tech debt, especially if they can be linked directly to KPIs.
  • Continually re-evaluate your tech debt strategy, particular at key moments in your startup’s growth journey (new funding, new product direction, new employees).

Acknowledgements

This article improved dramatically from the comments and suggestions from many of our colleagues. Our thanks to Martin Fowler, Tom Marsh, Andrew Buchanan, Ryan Puskas, Ahmet Sakar, Ryan Sawson, Kennedy Collins, Shea Clark-Tieche, Thomas Donahue, Christopher Hastings, and Yue Liang.

A Sneak Peek at BoxLang's Module Feature

Mike's Notes

Ray Camden gives a code demonstration on building a BoxLang module.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

08/05/2025

A Sneak Peek at BoxLang's Module Feature

By: Raymond Camden
raymondcamden.com: 07/05/2025

My name is Raymond Camden. I'm a married father of eight living in beautiful Lafayette, Louisiana. I'm a developer evangelist currently looking for next job. Most of my time is spent writing, researching, or presenting. When I'm not behind a computer, I'm an avid Xbox/Playstation player, enjoy movies, and read like crazy.

Last week I attended and spoke at Into the Box, a conference hosted by the Ortus folks, the company behind BoxLang. While there, I attended a talk from Brad Wood on how BoxLang's module system works. I've been using modules with BoxLang since I first started playing with it. It's how database support is added, mail, PDF, and more. You can see a list of currently released modules here. I had been curious as to how this works so I was excited for Brad's presentation. While hearing him go over the details, I got even more excited, and literally built a 'hello world' module in a minute or so while he talked.

I'm calling this blog post a "sneak peek" because while the module system is baked and ready (as apparent by the existence of multiple modules you can use now), what isn't ready yet is the documentation for creating your own modules. I've got a bug filed for this and will probably take a stab at writing this soon, but in the meantime I wanted to share some details about what you can do and how it works. Thanks go to Brad Wood for detailing this in his presentation and I'm using his slides as my reference (for now).

What Modules Provide

When a module is registered with BoxLang, it can add to the language:

  • BIFs (built in functions)
  • Components (these are tags, and can be coded to allow/disallow or require body arguments)
  • Classes (which you can then load via a defined import path)
  • Jars (for both usage as is, or within the module itself)
  • JDBC Drivers
  • Interceptors (more on this topic soon, but it's incredibly low level code integration)
  • Custom variable scopes

This isn't even 100% of what can be added, but gives you an idea. Taking just the first bullet point above, I could write a module that adds the raymondCamden() function to BoxLang. Once installed, your code could then just make use of it. There isn't any name spacing for these functions so it's something to consider when authoring, and installing, as it's possible two modules could use the same function name, but arguably that's probably not much of an issue right now - just something to keep in mind.

It's also important to note that modules can be defined for the entire server or just for one application (a web app or even just a CLI script).

Ok, cool, let's make one!

Making a Module

Before we begin, note that modules can be written in Java or BoxLang. I don't write Java (well, I can, I just don't want to) so this post is focused on BoxLang modules.

At the simplest level, a module is a folder with various things under it. At minimum is a ModuleConfig.bx class. This class has methods for configure, onLoad, and onUnload. The last two let write custom logic for when the module is loaded or disposed, while configure lets you work with any settings your module may have. The class can also be empty if you don't need anything, but I believe you still need the file there.

Next is the bifs folder. This is where you define built in functions that your module provides.

Then you've got a components folder. Any component here will be available to BoxLang via a defined class path.

And finally, a libs folder where any jars will be loaded and available to your code. What's cool is that a jar here will be specific to your module, which means if another module uses a similar jar, or a different version, you won't have any conflicts.

Creating BIFs

For me, I think most of the modules I may end up creating will focus on adding new functionality to the language via new functions. To do this, you simply drop a class in the bifs folder such that the name of the class is the name of the function (although you can tweak that via metadata).

At minimum, your class will have a @BoxBIF annotation and an invoke method which is run when someone calls your function.

How about a real, if incredibly trivial, example? I'm going to start in a folder and under it, create a new folder boxlang_modules. I'll place my modules under this for testing. Next, I'll create a folder for my specific module, which I'm calling cat. In there I'll add this ModuleConfig.bx:

class {

	public function configure() {
		println('configure');
	}

	public function onLoad() {
		println('onLoad');
	}

	public function onUnload() {
		println('onUnload');
	}

}

I'm not actually doing any logic here, but it shows the event handlers in action. Next, I'll create a bifs folder and in that, a file named meow.bx:

/**
 * This is a BOXLANG BIF
 *
 * Annotations you can use on a BIF:
 * <pre>
 * // The alias of the BIF, defaults to the name of the Class
 * @BoxBIF( 'myBifAlias' )
 * @BoxBIF( [ 'myBifAlias', 'myOtherBifAlias' ] )
 * @BoxMember( 'string' )
 * @BoxMember( { 'string' : { name : '', objectArgument : '' }, 'array' : { name : '', objectArgument : '' } } )
 * </pre>
 *
 * The runtime injects the following into the `variables` scope:
 * - boxRuntime : BoxLangRuntime
 * - log : A logger
 * - functionService : The BoxLang FunctionService
 * - interceptorService : The BoxLang InterceptorService
 * - moduleRecord : The ModuleRecord instance
 *
 */
@BoxBIF
class {

    function invoke(string msg="meow") {
        return "Kitty says #msg#";
    }

}

On top is a bunch of examples of how the annotations can configure how the BIF runs. I mentioned above the name of the file defines the name of the BIF, but you can customize that with either one or multiple names. Another really cool aspect is that you can define member functions that work with core BoxLang types, so for example, defining an array member function.

In the example above, I'm just going for the defaults which means I've added meow to BoxLang. The invoke message defines one argument with a default, but obviously you can do whatever your little heart desires here.

To test, I can go within the folder I created and use the CLI like so:

boxlang --bx-code "meow('ray')"

And this gives:

configure
onLoad
Kitty says ray
onUnload

I can also create a file, let's say test_meow.bxs, and use it that way:

/*
Just a quick test script for a module
*/

msg = meow('Raymond');
println('My custom module returned: #msg#');

I pushed up these demos here (https://github.com/ortus-boxlang/bx-demos/tree/master/modules) if you want to take a quick look.

A "Real" Module

So as I said, I played around with a module while listening to Brad (yes, I can multitask!) but later, worked on something a bit more real... RSS support. Back in February I blogged about using Java libraries in BoxLang and as part of that, I made use of a jar that does RSS parsing. It was pretty easy to do, but even simpler when acting as a module. You can see the final code on the repo I set up for it, but let me explain how it was built.

First, I created an empty ModuleConfig.bx. Literally just:

class {

}

My module doesn't need to check any settings or load anything by default, so it's just an empty class. I then added my RSS jar under the libs folder. Finally, I added a BIF called rss.bx:

import com.apptasticsoftware.rssreader.RssReader;

@BoxBIF
class {

	function init() {
		variables.rssOb = new RssReader();
	}

    function invoke(urls) {
	
		if(isSimpleValue(urls)) urls = [urls];

		items = variables.rssOb.read(urls).sorted().toList();

		return items.map(i => {
			result = {
				title:'',
				content:'',
				pubdate:'',
				link:''
			}

			i.getTitle().ifPresent(t => result.title = t);
			i.getLink().ifPresent(l => result.link = l);
			i.getContent().ifPresent(c => result.content = c);
			i.getDescription().ifPresent(c => {
				/*
				 So, some feeds had content, some had this. I think it is safe
				 to say that if this exists and content is blank, just overwrite.
				*/
				if(result.content == '') result.content = c;
			})
			i.getPubDateZonedDateTime().ifPresent(d => {
				result.pubdate = parseDateTime(d);
			});
			return result;
		});

    }

}

This adds rss() to BoxLang and supports either a simple URL for parsing one feed or an array of feeds to parse multiple at once. So for example:

feedItems = rss('https://www.raymondcamden.com/feed.xml');

Would return an array of items from my feed. Simple as that!

More to Come

So as I said in the beginning, there's a lot of power here, but it all needs to be properly documented. I'm going to be helping with that effort as soon as possible. One of the exciting things about BoxLang is how configurable it is at such a low level. Let me know what you think and give it a shot yourself.

Ben Nadel on HTMX

Mike's Notes

The Pipi 9 CMS Engine (CMS) uses HTMX in the rendered User Interface (UI). HTMX interacts directly with the DOM.

Recently, Ben Nadel has been running a fascinating series of posts on his blog about his coding experiments with HTMX.

He shares a lot of CFML code on his blog, which is freely reusable. I learn a lot from Ben.

I will update this page with additional references to Ben's HTMX posts.

There is a repository on GitHub for this experiment.

Resources

References

  • Hypermedia Systems by Carson Gross

Repository

  • Home > Ajabbi Research > Library > Subject > CFML
  • Home > Ajabbi Research > Library > Authors > Ben Nadel
  • Home > Handbook > 

Last Updated

10/1/2026

Ben Nadel on HTMX

By: Ben Nadel
bennadel.com: 2025

A Passionate Web Developer With 26 Years Experience.

"I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self! - Ben Nadel"

You Can CMD+Click To Submit A Form To A New Browser Tab

By Ben Nadel on May 29, 2025

In the past couple of months, I've been digging into the HTMX JavaScript framework as a way to layer dynamic client-side functionality on top of my ColdFusion applications. I'm enamored with HTMX; but, unlike as with other frameworks such as Angular or Svelte, HTMX doesn't want to be an end-to-end holistic solution. Which means, I—as the developer—have to make a lot of decisions about where logic should live; and, whether or not something is a good idea. This is turning out to be a lot harder than I first imagined...

read more

Creating An hx-event Extension For Triggering Events In HTMX

By Ben Nadel on May 21, 2025

I'm still very deep in the "learning phase" of using HTMX, trying to understand how things are wired together and where the jagged edges might be. As a code kata, I wanted to see if I could create an HTMX extension that would trigger an event instead of an AJAX call. This way, I might be able to click on a button and bubble a synthetic event up the DOM (Document Object Model) tree to another element that is using an hx-on:{event} binding...

read more

Considering The Functional Requirements Of A Modal Window In An HTMX Web App

By Ben Nadel on May 17, 2025

To start with, I'm not a huge fan of modal windows in a web application. InVision used a ton of modal windows; and it felt like we painted ourselves into a corner that we couldn't get out of. My ambiguity on the matter has only been hardened by articles such as "we use too many damn modals", "modalz modalz modalz", and "who's afraid of a hard page load". That said, modals—and modal-adjacent experiences—do have their place. And, as I start to think about this experience in the context of an HTMX-powered web app, I wanted to come up with a set of functional requirements that I could aim for...

read more

Using A No-Content URL To Help Manage DOM Caching In HTMX And ColdFusion

By Ben Nadel on May 14, 2025

When you hx-boost / AJAX'ify your ColdFusion application with HTMX, HTMX will cache the state of the DOM (Document Object Model) as you navigate from page to page. This way, when you hit the browser's back button, HTMX can restore the previous DOM state, pulling it out of the LocalStorage API. The caveat being, HTMX only does this in response to an AJAX request. Which means, if we want to cache the current state of the DOM, we must issue an AJAX request and we must change the URL...

read more

HTMX Extensions Have Access To An Extended API

By Ben Nadel on May 12, 2025

The HTMX JavaScript framework exposes a public API that is a mixture of convenience methods, such as htmx.on() for event-binding; and framework-specific methods, such as htmx.process() for initializing externally-loaded HTML fragments. When creating an extension in HTMX, the extension's init() method is passed an api argument. At first, I thought this was just an abstracted reference to the aforementioned public API. But, upon further inspection, this reference contains an extended API to help specifically with HTMX extension authoring...

read more

What Happens When You Mutate The DOM Outside Of HTMX

By Ben Nadel on May 10, 2025

In a previous post, I looked at using Alpine.js inside an HTMX application. Since Alpine.js uses the MutationObserver API to observe changes within the Document Object Model (DOM) structure, it doesn't much matter where mutations are initiated. HTMX, however, isn't quite so dynamic. As such, I wanted to sanity check what happens when DOM mutations are initiated outside of the HTMX life-cycle...

read more

Returning JavaScript Tags In HTMX And ColdFusion

By Ben Nadel on May 8, 2025

The primary mechanic of HTMX is swapping out branches of the DOM (Document Object Model) in response to user interactions. Yesterday, however, I had to update a bunch of form inputs based on a <select> change. I wasn't sure how to do this in the cleanest "HTMX Way". Eventually, I settled upon a happy medium in which I use HTMX to execute one-off JavaScript task via an Out of Band (OOB) swap of a <script> tag...

read more

Using Stale-While-Revalidate Cache-Control Headers In HTMX And ColdFusion

By Ben Nadel on May 6, 2025

In my ColdFusion applications, unless I'm generating dynamic images that need to be cached, I almost never use the Cache-Control HTTP headers. Historically, all of my "caching needs" for perceived performance have been handled in my client-side Angular controllers. But, the HTMX JavaScript framework is all about leaning on the web platform to drive application mechanics. As such, I wanted to see how I might use the stale-while-revalidate response directive to boost performance in an HTMX and ColdFusion application...

read more

Keyboard Command Extension In HTMX And ColdFusion

By Ben Nadel on May 3, 2025

Earlier this week, I took a look at using extensions in an HTMX and ColdFusion application. Extensions allow us to tap into the event life-cycle of nodes withing the document object model (DOM); which, in turn, allows us to augment the document behavior as HTMX swaps content into and out of the rendered page. For this follow-up post, I take inspiration from the Docket app by Mark Story. Mark's HTMX and PHP app allows keyboards events to trigger DOM interaction. I wanted to try building something similar for a ColdFusion demo...

read more

Exploring Extensions In HTMX

By Ben Nadel on May 1, 2025

The HTMX JavaScript framework operates by traversing your Document Object Model (DOM) looking for elements that match a set of criteria, such as the existence of an hx-get or an hx-post attribute; and then, processes the matching elements by applying new behaviors. As it orchestrates these modifications, it emits a series of events on the DOM. Informally, these events can be a way for any JavaScript library to hook into the HTMX life-cycle by consuming the DOM as an event-bus; but, HTMX also provides an official way of doing this by using extensions...

read more

Using :scope To Identify The Host Element In .querySelectorAll()

By Ben Nadel on April 29, 2025

Here's a helpful tip that I learned the other day. When using a Document Object Model (DOM) method to query for HTML elements using a CSS selector (ex, .querySelectorAll()), the :scope pseudo-class can be used to identify the host element on which the method is being called. This allows us to use the direct descendant selector (>) to target children of the host element...

read more

Using hx-preserve To Persist Elements Across Swaps In HTMX

By Ben Nadel on April 27, 2025

With HTMX, you can use the hx-boost attribute to AJAX'ify navigation in your ColdFusion applications. Boosting pages increases the complexity of the application and can lead to some strange JavaScript behaviors. But, this trade-off in complexity ushers in the ability to maintain some state across pages. And one way to maintain state in HTMX is with the hx-preserve attribute...

read more

Previous / Next <CSS Selector> Both Use A Depth-First Algorithm In HTMX

By Ben Nadel on April 26, 2025

In the HTMX JavaScript framework, the hx-target attribute can reference other elements in the DOM (Document Object Model) by using CSS selectors, convenience keywords (such as this and next), and a combination thereof. Most of these are relatively straightforward; but, I ran into surprising results when using previous {CSS selector} and next {CSS selector}. I had read the documentation and interpreted it as meaning that the targeted elements were direct descendants of the same parent element. But, this is incorrect. Internally, HTMX is using a depth-first traversal algorithm—from the document root—to find the target nodes; and then, selects the one closest to the trigger element based on a depth-first node traversal order...

read more

Fetch API Will Propagate Non-POST Methods Upon Redirect

By Ben Nadel on April 26, 2025

In the book Hypermedia Systems by Carson Gross, the authors mention that the browser will propagate the HTTP Method upon redirect. In all my years of programming, I had never seen this; so, I sanity checked it and put the issue to rest in my head. Then, yesterday, I was listening to the Remote Ruby podcast with Chris Oliver and Andrew Mason, and they too mentioned this HTTP method propagation specifically for the JavaScript fetch() API. I feel like I was taking crazy-pills; so, I went to sanity check it again; and, discovered that the fetch() API will propagate the HTTP method; but, only for non-GET/POST methods...

read more

Using Alpine.js In HTMX

By Ben Nadel on April 25, 2025

The HTMX JavaScript framework allows us to move a lot of state management out of the browser and back into the ColdFusion server where the "source of truth" resides. But, not all interactions—and not all state—need to be sourced from the server. In such cases, we can use Alpine.js to provide light-weight state management and event binding in the browser. That said, both HTMX and Alpine.js want to "manage the DOM" and the event-bindings. As such, it's not obvious that they'll play well together. To get a sense of where the points-of-friction might lie, I wanted to put together a small demo that uses both HTMX and Alpine.js as well as the hx-boost attribute to "AJAX'ify" the page navigation

read more

Filtering HX-Trigger Server Events In HTMX And ColdFusion

By Ben Nadel on April 22, 2025

When building Single-Page Applications (SPA) with Angular and ColdFusion, one common pattern that I used was to return an event in every mutation-based API response. This event was then triggered on a client-side event-bus; and, various Angular components could respond to it as needed. In an HTMX multi-page application (MPA), we can use the HTTP response header, HX-Trigger, to achieve similar outcomes. And, we can use the filtering capabilities of the client-side hx-trigger attribute to limit the scope of this client-side response...

read more

Conditionally Preventing HX-Boost In HTMX Using An Extension

By Ben Nadel on April 19, 2025

In an HTMX application, the built-in hx-boost attribute tells HTMX to intercept all form and anchor interactions and "AJAX'ify" them. That is, prevent the default browser behavior and re-implement it using AJAX and the History API in order to side-step a full page-load. This is a neat feature; but, it doesn't have any hooks (that I can find) to conditionally prevent hx-boost from acting on a given link. Such a hook would be necessary when integrating content that comes out of a content management system (CMS). To try and add such a hook, I've created my first HTMX extension, prevent-boost, that can conditionally prevent boosting based on RegEx patterns and / or filter callbacks...

read more

Comparing Undefined Values With Optional Chaining In JavaScript

By Ben Nadel on April 17, 2025

Yesterday, while working on some HTMX experiments, I stumbled upon a feature of JavaScript that I didn't known: the undefined value, when compared to Booleans and numbers, always returns false. I've always thought of undefined as a falsy value (which it is); so, I was very surprising to see that undefined did not soft equal false. But, this turns out to be hugely beneficial when it comes to the optional chaining operator (aka, the safe navigation operation) in JavaScript...

read more

HTTP GET And HTTP POST Are Sufficient (For Me) In ColdFusion

By Ben Nadel on April 14, 2025

As of this writing, HTML supports two different HTTP methods: GET and POST. And for the last 25-years, I've been building web-based applications very successfully on top of these two operation. Many people, however, are not satisfied with these limitations; and, would like to see PUT, PATCH, and DELETE added to the list of natively supported methods in HTML. Personally, I'm quite satisfied with GET and POST and haven't really experience any pain-points. So, I thought it might be interesting to try and articulate my perspective....

read more

ColdFusion And HTMX Contact App

By Ben Nadel on April 13, 2025

A couple of weeks ago, I read Hypermedia Systems by Carson Gross. The book is an introduction to HTMX - a JavaScript library that extends the HTML specification and brings dynamic single-page application (SPA) type features to a traditional multi-page application (MPA) architecture. Some of the aspects of HTMX are fairly simple; but, some require a sizable mindset shift. As such, I thought it would be helpful (for me) to take the "contacts app" that Carson builds in the book and translate it into a ColdFusion application context.... 

read more

Hypermedia Systems By Carson Gross

By Ben Nadel on February 18, 2025

Last week, Peter Amiri - maintainer of the CFWheels framework for ColdFusion - sent me a hardcover copy of Hypermedia Systems by Carson Gross, Adam Stepinski, and Deniz Akşimşek. This book argues for the benefits of "Hypermedia Driven Applications" (HDA); and how leaning into the foundational nature of the web as a unified interface and hypermedia platform makes applications easier to build and more accessible while still achieving a high-bar of interactivity. This book is clear, concise, and quite compelling. Coming in at around 300 pages, I finished reading it in two day—I just couldn't put it down.....

read more

A three-data-centre model for Pipi

Mike's Notes

On Monday, I met with DataGrid NZ to discuss the provision of a future data centre for Pipi. Here are my evolving notes. This will no doubt change with experimentation.

Update

See Apple's security model using stateless infrastructure.

Resources

References

  • Reference

Repository

  • Home > Ajabbi Research > Library >
  • Home > Handbook > 

Last Updated

25/03/2026

A three-data-centre model for Pipi

By: Mike Peters
On a Sandy Beach: 06/05/2025

Mike is the inventor and architect of Pipi and the founder of Ajabbi.

Perrine Dhalluin, CEO of DataGrid NZ, recently spoke at a meeting in April in Invercargill, New Zealand, about the new Data Centre to be built nearby in Makarewa. I got to meet her.

I had a further remote meeting on Monday, May 5, 2025, with Perrine Dhalluin and Pierre-Emmanuel Goiffon, DataGrid's CTO. The discussion was open and broad-ranging. I can work with these people.

Ongoing discussions and in-person meet-ups are planned.

Data Centre Requirements

The top priorities are

  • Reliability
  • Security

Reliability

This will require extensive power, cooling, compute monitoring, and connectivity redundancy. Also, plan for fires, tornadoes, earthquakes, undersea landslides, perimeter incursion, solar flares, etc.

Security

This critical infrastructure needs to meet any country's highest government security requirements. There must never be security or privacy breaches.

Data Centres

Because of its unusual architecture, Pipi needs three separate data centres that work together in a chain.

Rendering > Staging > Cloud

  • Rendering built enterprise applications
  • Staging update, localise and deploy enterprise applications
  • Cloud hosting enterprise applications

Rendering

The rendering farm data centre is like a CNC machine for software; it automatically builds massive SaaS enterprise systems for critical infrastructure to order. The first rendering rack is being constructed and will be operational later this year. Plan for growth over time.

Processes applied include;

  • Applying versioned industry schema, eg Snowmed, IATF
  • Ontologies
  • Laws of physics
  • Human language translations
  • Digital twins
  • Systems that learn by evolutionary algorithms and reproduce
  • etc

Digital twins of configuration settings and the path taken for each;

  • Enterprise customer
  • Industry
  • World model
  • Common core
  • etc

Rittal is the preferred supplier.

Staging

The staging data centre updates, localises and deploys the applications to the customer's cloud hosting environment and sends GitHub, open-source modules, and plug-ins. It needs to be somewhere very secure with excellent connectivity. Plan for growth over time.

Processes include;

  • Update servers
  • Additional prerendering
  • Localisation
  • Future AI usage
  • Documentation
  • etc

DataGrid NZ is the preferred supplier.

Cloud

Cloud hosting of the built enterprise systems for customer use in production.

Initial availability will be;

  • Apple
  • AWS
  • Azure
  • Digital Ocean
  • GCP
  • IBM
  • Meta
  • Oracle
With Ortus's help, more cloud hosts, including private clouds, can be added over time.

There is no preferred cloud supplier.

No Moats

The applications will not have moats and can use open API connectors to other systems, enabling the customer to choose. For example;

  • RDMS
    • MSSQL
    • Postgresql
    • Oracle
    • etc
  • GIS
    • MapInfo
    • ESRI
    • QGIS
    • etc.
  • Maths & Science
    • Wolfram
    • MatLab
    • Kepler
    • etc.

Platform Requirements

The common platform will be;

  • VM
  • Open-source BoxLang
  • JVM on Open JDK 21+
  • OS - Ubuntu Linux and Windows

Ortus is the preferred supplier.

Connectivity

This needs to be confirmed, but my understanding is that the Makarewa data centre will be connected via;

"Hawaiki Nui will connect South-East Asia, Australasia, and North America; the 12 fibre pair, 240Tbps cable will connect Singapore, Sydney, and Los Angeles, as well as New Zealand via Invercargill." - DataCentreDynamics.org

Before the data centre is built, two cables will come ashore at Oreti Beach, near Invercargill.

Update 15/03/2026

Datagrid New Zealand has received resource consent for the 78,000 square-metre data centre, which will be built in Makarewa, north of Invercargill. The company was founded by Rémi Galasso and Malcolm Dick in 2021. - RNZ

DataGrid Video