So I recently released some swizzyweb projects on npm so anyone can use them. If you are not aware of swizzyweb, it is a set of libraries that provide a framework for building, running, and hosting web services. You can checkout the announcement post here. But today I want to go over why I built all of this in the first place.
There are many reasons why I chose to build swizzy web, from just general curiousity to actual pain points I’ve faced building with typescript and nodejs. I’ll break down my motivations to personal reasons and technical reasons.
Personal Reasons
Skill development
I code in my free time to stay sharp and expand my skills that I otherwise would not get to do at work. I try to pick projects that I have not done before, and will introduce problems that I have not solved before. This will help me stay competitive in a brutal job market, helping me build critical thinking skills that can get rusty after years of working in an environment with consistent problems and tools.
Exploring new technologies
I also enjoy learning about new technologies, and I have gotten to the point where I’m really into creating my own new technologies. This was a huge motivating factor to me, I wanted to offer a solution that rethinks how we develop. The idea of dynamic hosting actually came from some research I did on module federation for the frontend. In module federation, you can have configurations that provide enough information to dynamically pull in frontend dependencies from various decoupled endpoints that are then rendered on the page.
So I thought, why not apply this to the backend too? Offer composable backend web services, where you can combine multiple implementations into a single backend endpoint. And what if you could build out a whole fleet of web services running across various ports, all managed through a single service. These problems just really interested me, which is the main motivation when starting this project.
Technical Reasons
State management is not included with express
When I was starting to build swizzyweb, I was also working on a database project. For that I used express for the api endpoints, but had to write a bunch of logic to actually get the database working. As I was building it, I found that I was performing a lot of operations in the controllers, and had dependencies on a storage object. In the beginning I just put that dependency as a global variable in the same express file as shown below:
const cache: LookupDiskCache<any, any> = getLookupDiskCache(tableName);
app.put(basePath, (req: Request, res: Response) => {
console.log(req.body);
const { key, value } = req.body;
try {
cache.put(key, value);
console.log('Put record into cache.');
res.status(200);
res.send('Put successfully.');
res.end({});
return;
} catch (e) {
console.log('Exception occurred putting entry into cache.');
console.log(e);
}
res.status(500);
res.send('Put failed.');
res.end();
});
But what if I wanted to shared this storage object as the project grew. What if I wanted to break out the project into multiple routers in seperate files, do I just copy the object to a shared file and use in both locations? What if that variable is overriden in a file and the reference is no longer shared between routers.
This type of state management was something that I explicitely wanted to solve in a managed way. I wanted endpoints to not have to worry about retrieving dependencies, but rather a system where they can define what they need and my framework takes care of providing it. This lead me to introduce state management as a core feature of swizzy-web-service, and resolves this pain point. As you can see below, all dependencies are not passes from a state object provided in the controller class:
protected async getInitializedController(
props: IWebControllerInitProps<ISwapCacheDbWebRouterState> & {
state: PutControllerState | undefined;
},
): Promise<WebControllerFunction> {
const getState = this.getState.bind(this); // Get from state
const logger = this.logger;
return async function PutFunction(
req: Request & KeyValueBody,
res: Response,
) {
logger.debug(req.body);
const { db, queryCache } = getState()!;
const { key, value, tableName } = req.body;
try {
await queryCache.deleteQueriesWithRecord({ key, value });
await db.put({ tableName, key, value });
logger.debug("Put record into cache.");
res.status(200);
res.send();
return;
} catch (e) {
logger.error("Exception occurred putting entry into cache.", e);
}
res.status(500);
res.send();
};
}
Object oriented is more maintainable
This is a contentious subject, but I find that object oriented architectures CAN be more maintainable than functional projects. I find this especially true in nodejs projects where functional style is the go to in most projects. So, I wanted to make my project object oriented.
Encapsulation
The entire swizzy-web-service package aims to encapsulate everything it needs to operate, and provide a minimal interface for users to focus on just implementing their business logic. The complexities of the project are hidden from users so that they can just focus on building their project.
Composition
SwizzyWeb relies heavily on composition. WebServices are composed of routers, and routers are composed of controllers. Everything is passed down top to bottom in this way, and state is transitioned between each by it’s parent. By using this composition structure, we can use polymorphism to inject verious implementations of the child components (ie: webservice -> router, router -> controller).
Polymorphism
Swerve depends very heavily on polymorphism to run web services. For swerve to work, a web service must export a getWebservice method that returns a web service implementation in it’s entrypoint. Swerve then calls this method to get an instance of the particular web service, bootstraps it, and manages it throughout the services lifecycle. This means that each package must implement a webservice implementing the base web service interface, and / or extending the base web service class. By ensuring that all web services implement the same base methods, swerve is able to manage any impementation regardless of the specific business logic they use.
Development and Deployment Flexibility
One of the major things I wanted to solve with SwizzyWeb was making monolith vs microservice two way door decisions. The composability features of SwizzyWeb means that you can bundle multiple services into a single web server. This means that you can architect your stack with multiple smaller microservices and deploy them as a single monolith when your services are receiving less traffic, but seperate them out into seperate microservices later without any code changes.
Another benefit of this structure is to have a more organized development workflow. If you are working in a multi team environment, each team can own a seperate swizzy web services and deploy them together. This way, as each team is developing, the won’t have worry about merge conflicts related to the partner teams web service. So in a sense, SwizzyWeb solves an organizational problem, without locking you into a particular deployment strategy.
Wrapping up
So as you can see, there are many reasons I build SwizzyWeb, and many more I didn’t put down here. It was both intended to be a learning experience and skill building activity, as well as an attempt to solve problems I face everyday. I hope that others can find this useful and make your development process faster, better, and more flexible. If you want to get started with SwizzyWeb, you can find the projects on swizzyweb.com, npm, or swizzyweb@github. Docs for swizzy-web-service and swizzy-common.
Keep it swizzy,
J out