-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
928ce40
commit af16302
Showing
1 changed file
with
169 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
--- | ||
title: When not to create a new web service | ||
excerpt: From a business perspective, web services are the thing to do these days. From an engineering perspective they are not necessarily the right choice. | ||
published: false | ||
tags: [ microservice, service, library, sdk, api ] | ||
--- | ||
|
||
"Make a bajillion dollars writing a web service" they say. "Microservices are the modern design pattern" can be found on | ||
numerous tech blogs. There is a lot of hype for "web services". | ||
|
||
If you are a software engineer looking to stay up to date you are likely being bombarded with business people, and "tech | ||
authorities" pushing you to build web services. Their reasoning isn't necessarily incorrect, but the conversation is not | ||
including consideration of when you shouldn't build a service. When there is a more effective design that gets you what | ||
you want, and it isn't new. | ||
|
||
So you have a system you are building, and this system executes compute operations based on user (or other agents) | ||
requests. It doesn't matter the specifics, just that a computer somewhere needs to execute a series of actions. Where | ||
should this occur? | ||
|
||
As a good engineer you are of-course designing towards high availability, fault tolerance, infinite scalability, | ||
security, and low latency/geographic co-location to name a few buzzwords. This can become non-trivial quickly if your | ||
service is popular. It is too bad there isn't a magic bullet, a perfect solution that solves these problems cheaply and | ||
simply. There is! Well, sort of, with caveats that we will now discuss. | ||
|
||
So, why not build a webservice? What is this magic bullet alternative that is promised? | ||
|
||
It all boils down to where you choose to do the compute operation and why. You are already shipping a client library or | ||
application, why not do the operations there? | ||
|
||
It might be easier to first explore when you *should* build a web service. | ||
|
||
# When should you build a web service | ||
|
||
A web service is a good choice when your design requires one of the following. | ||
|
||
## Trust | ||
|
||
Trust is probably the most common reason to not do the operations client side. There are two major aspects to consider | ||
when evaluating where to place an operation. | ||
|
||
### Tampering | ||
|
||
Your system can likely be disrupted by a bad actor. You need to own the system sensitive compute operations occur on | ||
such that it can't be readily tampered with, without first compromising your system security. | ||
|
||
#### Access control | ||
|
||
Your service likely serves more than one client, and you need a way to prevent one client from accessing anothers (or | ||
internal) data. This is likely the most common reason to create a web service. | ||
|
||
The data may not be specific to any of your clients, but represents an asset of your business to which you need to | ||
control access to. You may need to prevent any one person from accessing your entire dataset, either through rate | ||
limiting, partitioning the data, or some other criteria. | ||
|
||
#### Consistency | ||
|
||
Your system may depend on several resources being in a consistent state in order to operate correctly. If you have a | ||
compute operation that exists to ensure this consistency, then you will want to prevent bad actors from tampering with | ||
it. A bad actor could potentially interrupt a sequence of operations that must occur with a request, breaking | ||
consistency. | ||
|
||
### Intellectual Property | ||
|
||
If the value of your service is in the technology developed within its source code, then it may be a risk to your | ||
business to ship that technology directly to your users. If normal copyright/patent methods of protecting your IP are | ||
not applicable or effective enough compared to the business risk and secrecy is the only means of preventing someone | ||
stealing your IP, then you will want to make your service available in a way that allows you to keep control. | ||
|
||
This isn't a catchall reason to never distribute your IP though. If the logic of your web service is simple enough that | ||
someone can reproduce it, then they can simply copy the web API you publish and reimplement. | ||
You would be better off providing a better performing solution, and focus on having the market adopt your solution over | ||
competitors. You will maintain control of your product by controlling the distribution stream. If everyone is importing | ||
your library, or training against your application, then it will be harder for competitors to convince people to put in | ||
the effort needed to refactor their code to a competing library, or retrain their staff to a different application | ||
interface. | ||
|
||
## Compute resources | ||
|
||
Your users may be making requests from hardware (IoT devices, mobile devices) with limited resources (CPU, Memory, | ||
Storage, throughput). The compute operation may be so intensive that reasonable consumer hardware could not complete it | ||
without impacting the users overall experience. Part of your services value then becomes providing sufficient compute | ||
resources to offload the operation to. | ||
|
||
If your service involves a large dataset, it may not be feasible to transmit the entire dataset to the client for the | ||
client to parse. You would need the operations that query the data to be co-located with the data. This dataset could | ||
also be a large set of binary dependencies. If your libraries dependency closure is large, and it would be burdensome to | ||
transmit it to the client, then you might be incentivised to house the closure and co-locate the functionality as a | ||
service. | ||
|
||
Your operation could be reasonably computed on the clients device, but the device is powered by a battery. To | ||
preserve the available power on the clients battery, it may make sense to offload the operation to compute | ||
hardware you own. | ||
|
||
A more niche factor is you may have a compute operation that depends on a specific CPU architecture. If you can't | ||
control what architecture your clients are using then you will need to provide your own compute hardware with the needed | ||
architecture. | ||
|
||
## Asynchronous | ||
|
||
If an operation *must* occur, and the client is not guaranteed to complete it or part of the services offering is that | ||
it ensures the operation is completed. Offloading the operation to a more robust, highly available system with automated | ||
fault remediation would be a good choice. | ||
|
||
A simple example of this would be a message queue as a service. The client can pass a message to the queue service and | ||
the service will guarantee the message is either delivered or a fault is reported. This is good value when the client | ||
has intermittent connectivity or can unexpectedly terminate. | ||
|
||
# When should you not build a web service | ||
|
||
If none of the above criteria apply, then distributing your applications logic as a library/SDK or built into a client | ||
application is hard to beat. | ||
|
||
If your target is high availability: you can't get more available than serving a request within the same process as | ||
where it was requested. | ||
|
||
If your target is fault tolerance: having the requester able to instantly receive errors in its code path and deal with | ||
them within the context of where the request is being made doesn't get any more robust. This is compared to having the | ||
requester send the request and then have to asynchronously resolve and remediate errors. | ||
|
||
If your target is infinite scalability: you can't scale better than having the requester provide the compute resources | ||
to serve the request. | ||
|
||
If your target is security of the client data: you can't get more secure than the clients data never leaving their | ||
device. | ||
|
||
If your target it low latency/geographic co-location: you can't get more co-located than serving the request in the | ||
same memory allocation as the requester. | ||
|
||
## Versioning | ||
|
||
A services interface or API is a contract between you and your clients. Generally once you publish an API, you can't | ||
change it without impacting your clients. Even if you are good at not making breaking changes, your service will | ||
undoubtedly exist in an environment with numerous dependencies that are changing. This means that even though the API | ||
doesn't change, the services implementation and its behaviour does. | ||
|
||
Unless you are willing to maintain multiple instances of your service for each version of its entire dependency | ||
closure (which can get expensive/infeasible quickly), then you can't version your service. If you change your service, | ||
even in un-anticipated ways, it can impact the clients. They have no way to version their dependency on you. A library | ||
on the other hand can be versioned, including its entire dependency closure. The clients are free to upgrade when they | ||
are ready, and able to re-test their integration for impacting changes. | ||
|
||
## Infrastructure | ||
|
||
Services require supporting infrastructure. If you are choosing to do the compute operations on systems you own, then | ||
you need to own those systems in some way. You can outsource the hardware and much of the orchestration, but you still | ||
need to own it and ensure it is secure, maintained, and healthy. With a library, the client provides the compute | ||
infrastructure. | ||
|
||
# Factoring your design | ||
|
||
Some of your services components will likely meet one of the criteria that justify it as a service. You can still gain | ||
the benefits of a library while also implementing a service. This is where microservices shine. Boil your | ||
services logic down such that all operations that can be done client side remain there, only sending requests to a | ||
service when the client is not suitable. | ||
|
||
This also applies to internal services talking to each other. Does it make more sense for these services to talk to a | ||
new intermediate service rather than just importing a new library? Always adding libraries to a existing service can | ||
result in monolith services being built. These are generally seen as compromising to a high availability model. | ||
Monoliths can also be unwieldy when updating a deployment due to a change in one of the libraries. This is generally a | ||
symptom of bad CI/CD or unit testing. If you opt for a service over a library to maintain some level of isolation of | ||
your operations, then you are just trading unit tests at build time for integration tests at deploy time. | ||
|
||
You might try to make the argument that there should be a separation of concerns between services. When comparing a | ||
service to a library, the division is there either between the service interface or the library interface. With a | ||
service you are only trading a stack push for a network packet. | ||
|
||
This isn't an argument for monolith services or advocating to always build libraries over services, it is an argument | ||
that you should consider where you are locating the compute operations and why. Hopefully you take these considerations | ||
into your next design meeting, and if you decide to build a library, maybe open-source it. |