I think this sentiment is a partial cause of the trend towards self-hostable, downloadable software too.
The customer has a cost when they operate a library instead of consume a service, no doubt. They also get more control (no surprise upgrades, availability is their responsibility) and assurances (no worries about the service suddenly being end of lifed).
Exactly. It'll break sometime (it's software). So when do you want it to break? And is the benefit of controlling/planning the timing of that breakage worth the cost of operating the software?
As always, it depends. Questions I'd ask:
* How critical is the software to your application's proper functioning?
* How big is the team?
* Who are your customers and what are customer expectations around your application's proper functioning?
* How often does the library change?
* What expectations does a service set around backwards compatibility? What commitments are made?
>When you use an outside service, they might break your system at a time of their choosing.
Well sure, but if this is important to you, you might consider a contract with the company that lays out cases and conditions where breakage is acceptable.
Yes. And if an updated library breaks you, you can fix it easily by rolling back to the earlier version of the library. If an outside service breaks you, you're hosed until they fix the service or you rework your code to route around the breakage.
Services usually depend on databases. Libraries usually don’t. Either you need to support every storage backend your users might have, require them to write an integration layer from your generic hooks, or expect them to provision and manage new storage when using your library. In any case you are asking them to do a lot more work (manage the data) and in some sense breaking encapsulation by making them responsible for this.
Great point. In my opinion it is possible and maybe even ideal to do both: make it easy for anyone to run their own service while also running your own service so that users have the option to not have to manage the data, patching and ops side.
This is interesting, makes me wonder if a "dockerised" database is something people could use. I mean a database frontend with its own language/protocols/whatever that allows you to define the data structure but leaves the specific storage engine or format as a backend detail that can change from platform to platform.
> I mean a database frontend with its own language/protocols/whatever that allows you to define the data structure but leaves the specific storage engine or format as a backend detail that can change from platform to platform.
Nah. It's not that. We lack a concept that can organize storage. Let me illustrate this.
So, until some years ago there was complete nonsense and anarchy in Linux networking management. That is until we got the "ip" program. There's still nonsense and anarchy, because the "ip" program doesn't cover everything, but it's on the right track to organize everything Linux knows about networking under one roof. So, vendors today, like, say, Melanox (i.e. NVidia) choose to interface with "ip" and work with that stack rather than invent their own interfaces.
When it's extendable in predictable and convenient ways, user will extend and enrich functionality.
Now, compare this to Linux storage... I want to scream and kill somebody every time I have to deal with any aspect of it because of how poorly mismanaged it is. There's no uniformity, plenty of standards where at most one is necessary, duplication upon duplication, layers... well, forget layers. Like, say, you wanted a RAID0, well, you have MD RAIDs, you have LVM RAIDs, you have ZFS RAIDs, you have multipassing with DM (is that a RAID, well sorta' depends on what you expected...) also, well, Ceph RDB are also kind of like RAIDs, DRBD can also sort of be like a RAID...
Do you maybe also want snapshots? How about encryption? -- Every solution will end up so particularly tailored to the needs of your organization that even an experienced admin in this very area your org is specializing will have no clue what's going on with your storage.
Needs can be studied, understood, catalogued, rolled into some sort of a hierarchy or some other structure amenable to management. We haven't solved this problem. But we have an even bigger one: no coordination and no desire to coordinate even within Linux core components, forget third-party vendors.
I blame the SQL "standard". It's a massive, unnecessary abstraction layer that only complicates attempts to build bridges between code and relational databases (which I believe is the most general-purpose paradigm).
Personally, I am working on a modern Python ORM for PostgreSQL and PostgreSQL alone.
Adding two numbers together takes on the order of a nanosecond. Doing the same thing using a rest / http service (like an idiot) in the same datacenter takes on the order of a millisecond. Six orders of magnitude actually.
I'm pretty sure the parent comment was about storage media, not about network hops and service boundaries. Also, extremely basic REST/HTTP services definitely do not take 1 millisecond even if you are bad at software - the overhead of that stack is in the tens of microseconds if you are doing nothing.
For the comparison being referenced here, if you want to compare RAM, the storage medium that backs compute, to modern persistent storage, here it is:
* 40 GB/s per DIMM vs 5-10 GB/s per NVMe SSD. At most one order of magnitude off, but you can pack enough disks into a computer that the throughput ratio is almost 1:1. AWS EBS is about 1 order of magnitude different here, and that is with network-attached storage.
* 100-200 ns latency (RAM) vs 10-50 us (fast SSD) - about 2 orders of magnitude, but also possible to hide with batching.
Isn't this what SQL is supposed to be? You bring the DBI for your database and plug it into the app. Shame that it doesn't work out so well in practice.
Cloud providers are (counter to intuition about their lock-in incentives) improving the space of this. Lots of tools now allow configuring storage by just pointing to various cloud stores, most often a S3 compatible api, not exclusively though.
K8s PersistentVolume is another decent shot at storage abstraction, only a bit raw.
Finally, more and more tools expect you to have a Postgres they can plug into as backend.
All above assumes you want to treat the library data as a big unknown blob. Once data start being corrupted and need bespoke repair, things are less fun. Access and retention is another fun rabbit hole.
SQLite alone doesn't help you get away with any of that. If you already know that a single machine is enough for you, then sure, SQLite is a fine choice. Availability needs alone often force you to run multi-node setups.
Most private software is, but there's an awfully large amount of publicly served software that can't fit into this model (also, any software that has network effects like twitter).
I'm maybe naive, but is it not possible to supply a repository interface for the user to implement? Bring your own glue?
The library uses only the interface to work with whatever orm/db connector exists in the client project.
If services at any given company all use a standard db library, it could even directly interface assuming your using that. I don't think we're talking about public apis and packages here.
It's only an unreasonable amount of work if you assume that the user is managing a separate storage backend for each library. If you take the Tim Berners-Lee approach (re: https://solidproject.org/) then each user is only managing one storage backend: the one that stores their data. The marginal cost of hooking in one more library to the existing backend is low.
We just have to get a little more fed up with all of these services and then the initial cost of setting it up in the first place will be worth it. Any day now...
I think most interesting web services are providing structured access to the same data for multiple people. A private, individual data silo wouldn't get the job done unless combined with some kind of message-passing. A silo to which users can invite peers is interesting, but it's an important characteristic of many web services that the specific read and write transactions allowed are application-defined... you don't actually want to give your collaborators general read or write access at the storage level.
For example, it's important that I can add this comment, and I can't delete your comment, but the moderators can. The "storage" software would have to know something about the business logic of a web forum to make that happen.
I think we just need smarter browsers which can be configured to know who we trust in which dimension.
If I want to leave a comment on an article and then delete it, I can publish the comment in my pod, and I can also publish the deletion. If you've got your browser in a mode where it's interested in my comments, it can pull in the data from my pod and render it in context with the article--whether or not the article's author cared to provide a comments section.
If you drop the idea that anyone is authoritative about how it all comes together on the viewer's screen, you can also dispense with the headaches of being that authority (e.g. services).
The title of the article literally says "where possible". You found a case when it's not possible, and decided to argue against that...
No, not all services come connected with a database. Alternatively, often times a database is an artifact of tenancy and the need to manage users which would not be needed, had the functionality be exposed as a library.
More importantly, whether users realize this or not, a library is more beneficial for them than a service in majority of cases. Much in the same way how it's almost always better to own something than to rent it.
Just to give some examples of the above: all the Internet of crap stuff, all sorts of "smart" home nonsense which requires that you subscribe to a service, install an app on your phone and send all your private data unsupervised to some shady Joe Shmo who you know nothing about. To be more specific, take something like Google Nest thermostat. There's no reason this contraption should ever go on the Internet, nor should it require to know your street address, nor your email etc. In fact, the utility it brings is very marginal (saves you few steps you'd have to make to reach for the boiler's controls to program it). It absolutely could've been designed in such a way that it doesn't connect to the Internet, or, at least, to never leave the local area network, and yet it's a cloud service...
Is this really a common scenario where there's a choice between these 2 options that isn't obvious? I've never considered libraries and services to be two equal options of distributing functionality, and you just pick one of them. It's usually a function of practicality and monetization.
Exactly. I've never run into a situation where there was even a choice.
Is it something that relies on a private database, queue, massive processing, dedicated hardware, shared state, something geographically distributed? It's a service out of necessity.
Or is it just a bundle of quickly executing code? Then it's obviously a library.
I've never seen anybody try to turn leftPad() into a service.
I've seen plenty of engineers try to turn trivial functionality like that into a microservice "to avoid version upgrade hell" (quote from one person in a previous job), or bundle what should be a simple self-contained (i.e. no-dependencies) library of functions (i.e. an API) into a REST/gRPC interface service. Microservices fad-following is as bad as TDD in this industry.
In my experience a new micro-service considered the default option for anything which could be done as a micro-service nowadays. Library API design is the lost art (almost). And the choose is not always obvious for non-technical reasons. Consider a following example: you have 3 micro-services X, Y, Z which need to interact with a platform G but this interaction requires non-trivial chunk of code. It can be done as a library or as a new adapter micro-service A which will encapsulate knowledge about platform G and will interact with X, Y, Z the way it would be easier for X, Y, Z developers to integrate. Micro-service will add network latency but will allow to make/deploy all changes only to service A. With a library one would have to test and release a new version and then ask X, Y, Z maintainers to switch to this new version. In some organizations it will be a very slow process because X, Y, Z can put an update request (from the library team) at the bottom of the backlog. With a micro-service A teams X, Y, Z would have much less power to stop/slow development.
This is making technology objectively worse to solve people problems, and this only expands.
More software needs iron clad leadership and control. Any organization that lacks this can’t help but produce shit software. There has to be a single person with real decision making power than can force upgrades and prioritization of work, and they need to be able to axe people or teams who can’t hack it.
OK but most business compete in a market. And the people they employ are in a labour market.
It seems that worse is better.
What we see is just another manifestation of the current economic paradigm. Waste is winning.
Making too many pairs of jeans so we burn tons of perfectly good new pairs daily wins.
Throwing hundreds of engineers on problems bashing out hundreds of thousands of lines of code sending megabyte messages between dozens of service instances in multiple kubernetes clusters appear to be a winning move. Otherwise someone would beat them, right?
Most engineering orgs don’t understand or feel “the market” until mass layoffs. They exit their companies with beautiful resumes touting micro services and k8s.
Worked a place where we switched from having a client library to making the other teams use our APIs directly themselves. Of course, we had to do the switch for them because they were too busy... Pretty much just copy-pasted the library code directly into their projects.
If you're distributing something publicly, it's fairly obvious which to pick, yeah.
It's less obvious for internal systems and architecture. For example, your company wants to add domain-specific auditing to all of your existing services. You could have every service add a library dependency that lets them just call `auditor.log(...)` and the library internally writes to storage. Or you could add an auditing service with a full HTTP/GRPC API. Or you could go halfway and build an auditing service but provide a library that acts as an interface.
There's no right answer for this IMO, all those approaches have pros and cons.
This was a really common dilemma at Amazon. The prevailing wisdom was opposite to the advice in the article though. Unless you had an exceptional reason, your functionality and data should be exposed as service, not a library.
Oh, absolutely. Service brings convenience to the backoffice and money! Libraries suck to support and hard to sell.
Of course users want libraries but vendors want services. There are plenty of examples where something could've been a library, if the vendor had user's interest at heart, but instead it's sold (or rather rented out) as a service.
Go to Amazon marketplace, for example. Virtually everything there is a product that should've been a library but is sold as a service...
Yeah but then who is responsible for maintaining the library? If you have a bunch of internal teams that depend on your library and there is an issue or a feature request, you're back to the same position of being the one that does the work to implement. Better to have a service IMO, you can get telemetry out of it and scale it out or replace internals without having to worry about who will get affected.
Many engineering organizations have "platform teams"--teams that have as their sole responsibility maintaining shared libraries and "core" services for the entire platform.
As opposed to being responsible for maintaining and doing operational support of the service...
I still don't get what advantage exactly you are expecting.
Scaling it is only ever a problem for the service; you don't even have to think about it in a library. You can get telemetry from a library just as well as from a service; your users may find that a bit invasive, but it's still way less invasive than calling your service. And you can replace the internals of whatever, without having to worry about who will get affected, that's what defines internals.
Because if you ship code to users (library), you lose control. You may want to change something, but your users will just tell you to go pound sand. Conversely, if you keep the implementation on your side (service), you get to control how things work and when things change, and your users don't have a say in this.
I don't see the issue here. If I release my_fancy_lib 2.0.0, and I have users who only ever want to stay on the last 1.x release, that's fine. It's no skin off my nose if users choose to stay on an old version forever.
I'm writing from the perspective of the user (which may be a developer using your product in their product). I don't really care if you view software as a knife or as a noose, I don't want to be coerced by the threat of either.
The difficulty is in maintaining your ability to send in the teeth of third-party block lists, which I think Stallman would likely rail against, so I concede the point.
I hope GraalVM's Polygot runtime will eventually allow more companies to go with libraries over services in more situations for multi-language businesses.
And Polylith for better reuse across projects generally.
this sounds very nice on paper (it sounds common sense trivial utopia that everyone sets out to do anyway, but somehow life, entropy and deadlines get in the way), but ... is there a bigger real life project using something like this?
I wish I could nuke GraalVM from the earth. It ruined my life for 2 years and I will never forgive it. It's a very stupid idea, pushed by CTOs that think Java's "write once, run everywhere" is still relevant. It literally runs every language slower and introduces all kinds of build pains.
Ugh, I can't believe I had to read your comment this morning. smh
It's all fun and games until you hit version n+1 or n+2, and often realize how slow many customers are to upgrade the library. Then there are the potential conflicts in your own dependencies. And let's not forget the occasional breaking change you introduced.
You are now sacrificing the money saved by not hosting to maintain what will likely be a growing matrix of possible versions, underlying assumptions, and my personal favorite: weird customer deployment scenarios that break your library functionality. Bonus points if you are developing in a language without strong typing, or need to integrate with a dependency manager.
Plus, if there was anything about your service that leveraged unique algorithmic improvements or some other proprietary tech, it is now at the mercy of anyone with a decompiler and sufficient time.
I suspect the author may change their mind if exposed to scaled up solutions and technologies.
"But this assumes that slow-to-upgrade users can have negative effects on everyone else. If one user can't have a negative impact on other users, then you don't care if some users are slow to upgrade; they're only hurting themselves."
There's still the support issue, I agree. If a customer paid you money and they are on version n-10, they still expect support.
> Plus, if there was anything about your service that leveraged unique algorithmic improvements or some other proprietary tech, it is now at the mercy of anyone with a decompiler and sufficient time.
This is a valid point. My answer would be: it's all tradeoffs, but if your secret sauce is so valuable that it would be worth decompiling and can't be protected with decent pricing, license terms and lawyers, run a service.
There are plenty of technologies that are not worth decompiling for your average business customers.
> It's all fun and games until you hit version n+1 or n+2, and often realize how slow many customers are to upgrade the library.
How is that different with services? I don't develop services, but I can imagine that before breaking the services, you have to poll with your biggest customers and you don't break until they are ready to move to the new version. The alternative I can imagine is to keep providing the old services with a grace period (e.g. /v1 will be available until dec next year).
Many changes are believed to be non-breaking and so you can be running only one version in prod. (Most of the changes believed to be non-breaking are non-breaking.)
With a library, you end up with many different minor or point versions running without control over it.
The idea is to link a thin client library into the user’s code.
Then the thick client is controlled server side by the devs.
If you design the thin and thick client intelligently, you can make a lot of changes to the system by merely modifying the thick client (which you control), without needing to update the thin client.
Problems tends to come in a few flavors, namely that this increases complexity and that some changes will always require modifying the thin client.
The biggest pitfall, though, is that you have to actually put thought into the thin client design. “Thin” is an emergent property of a well thought out client. You can’t just “touch thin_client.java” and think because you named it “thin” that it’s inherently decoupled from the service. You have to actually put thought into it.
Your example is exactly right. The service can support either v1 or v2 depending on the version of the thin client.
The main other technique is to have the thin client communicate in a relatively abstract and generic manner. For instead a “write()” endpoint that accepts an “options” dictionary.
“write()” is so generic that it probably won’t have to change, and changes in behavior can be modified by shoving stuff into dict, perhaps with a “version” field to instruct the server how to interpret the call.
When you add a new feature, you can bump the thin client to v2 and shove new options in the dict. Then the server can support v1 and v2. But also, you can modify the server to handle v1 differently. E.g the absence of a “disable_new_feature” key automatically opts v1 callers into the new feature.
I think the details are are really coupled into what you are engineering and what you want to roll out. There’s no magic bullet, and this increases the complexity of your code. Good engineering in my opinion is deciding when these approaches are worth it and how they should be implemented.
As per the article, the advantage of a library over a service is that you don't have the burden of maintaining the said service. How having a thin client and a server helps?
I also don't see how "shoving new options in the dict" can help. From my POV, if the client needs to be updated to benefit from the new features, then there is no way around it, work has to be done. From there, I much prefer having sensible name, parameters, etc. from a library that can be leveraged by static typing rather than a documentation for a JSON API (I know API-as-specs exist but I've never used, maybe that's why I think that).
I understand it may be different if the software is distributed through the package manager of linux distribution because you have to follow what's provided by the distribution. But for a commercial software you can probably bundle your own libraries in executable or compile it statically.
But I am not familiar with the difficulties of the above process so I am probably missing something.
You have the same problem with services and libraries when you’re introducing a breaking change. With services you can make non-breaking changes like security patches on the server side, without needing to coordinate with a customer.
That's not a great example given OpenSSL versions are famously backwards-incompatible. Older versions get ABI-compatible security patches because people put in the time to backport all of them to every version still supported, in many cases by distro package maintainers. It's exactly the situation libraries should generally avoid, because there are maintainance costs for older versions of the library as well as migration costs for all of its users.
The point is that someone is paying the cost of maintaining the library's ABI surface area even if it's not you. In a thread specifically for developers considering how to offer an interface to their software, I think it's only fair to recognize the costs of those different approaches. In that regard, OpenSSL is an extremely bad example, or an extremely good example of what not to do.
Simple answer is YOU control the deploy of a server in miroservices, so YOU decide when it goes live. You aren't playing spreadsheet telephone at scale to get everyone even im an internal org to update. If you're smart you have your callers send version information and you usually just add fields, only removals or renames cause version issues. If you're having everyone use a library they control when they update the logic and you have less telemetry when some wierd corner of your company is using out of date logic.
> It's all fun and games until you hit version n+1 or n+2, and often realize how slow many customers are to upgrade the library.
Given how frequently needless breaking changes are made, or features are removed and paywalled, I would consider this a feature rather than a bug. Sometimes I have higher priorities than working to support someone else's breaking changes.
Two years ago, I was using an official library for interfacing with a video chat service, and they decided to break the underlying API without updating their library, so I had to rewrite the library myself.
You are wrong by pushing your changes on "slow to update" customers. Customers know when to update much better than you do. If you are trying to update before they want to, you are doing them a disservice.
This attitude is inspired by the desire to sell more, and often times, it means to sell more unwanted crap, where customers are trapped by the "package deal", where they are either forced to update to gain useless features and the headache resulted from various inconsistencies and defects coming from the last update, or they are unable to get the product at all (because the provider cancels support or wouldn't sell older versions etc.)
In other words, you with straight face describe some really shady practice and you don't even realize how bad it sounds.
> This attitude is inspired by the desire to sell more, and often times, it means to sell more unwanted crap, where customers are trapped by the "package deal", where they are either forced to update to gain useless features and the headache resulted from various inconsistencies and defects coming from the last update, or they are unable to get the product at all (because the provider cancels support or wouldn't sell older versions etc.)
Selling more is how service providers are able to sell their generic service at a lower price than the cost you would incur by building it in-house. In other words, being forced to upgrade to accommodate features that other users want is the price you pay for sharing the development cost with those other users. That's not shady.
You assume I wanted to make the update in the first place.
I invite you to review roll outs of security updates. These are rarely about pushing new features, and may have nothing to do with my own code at all. It might just be a version bump to my dependencies config (if a dep manager with shared libraries is involved), or just a refresh of my flatpak equivalent. But, either way, shit breaks in weird and wonderful ways, and there is little I can do but wave my arms frantically at customers.
Yes you need to have n versions you support in parallel in the wild. One per released incompatible binary API (so in semver 1.x, 2.x etc). We support about 30 libraries with 5 ppl spread around a fortune 1000 org here and there. It’s quite tenable, we are just super strict regarding ticket policy and release notes, so we always know what’s released where. Just have a system. Stick to it. Keep things organized. Works like a charm. If someone wants a service out of those someone just packages them to docker and puts them to backemd somewhere. We can focus on pure business logic, someone else maintains services etc. Really nice setup.
I don't get it. Can anyone kindly share where this practice can be applied to a commercial software? I am struggling to wrap my head around "how run by user" works where they are using a commercial software service.
There's some confusion in this thread between "service" as out-of-process functionality called via IPC or over the network, and "service" as something done by a service-provider for money, such as emptying your bins.
Ultimately, it can't. Proprietary software has fundamental limitations that force proprietary software developers to choose technically inferior designs. It's why in the long run proprietary software is doomed.
"where possible" is a keyword here and easy to argue about. For example:
* my logic requires a database
* my logic should be isolated for compliance reasons
* my logic accesses a service which is not available for everyone
* my logic should be processed async and needs to store state, how do I make sure library owners have that environment
* customers embedding my library are not upgrading it frequently, which leaves us to support 15 years of libraries
* ...
Imagine a scenario, your service is a low traffic, but service which embeds you as a library is a high traffic with many instances and always opens DB connection.
Why should you optimize your Database for high traffic use case, when your use case is really a low traffic?
And then repeat this for 10 other libraries and library owners. Everyone is optimizing for nothing.
make it even more difficult, 100 different types of services with different traffic patterns are embedding your library with different behaviours when it comes to managing DB connection state
Scenario: your service is accessing a service which exposes PII data and you only process them.
Service which embeds your service enabled audit logs of network requests and made it visible to everyone in the company. You have created a risk unintentionally
It’s hard when you have N services that need a lockstep migration from v1 to v2 of the library. I tend to agree with much of this but it’s not a one size fits all thing.
Not as a primary interface. By all means provide a wrapper around the library that does that, for users to use on the command line, but there should be an underlying library for other programs to use.
Problem with libraries are that they tie the user to a specific language/OS. A cli tool eating and spitting ASCII/UTF-8 is about as cross platform as it gets.
i get the benefit of offloading the admin costs onto your consumers, but, as always, the devil is in the details. i've met both cases where a library should've been a service, and vice versa. this advice is way too broad and abstract to be practical
Those aren't "either or" things. Usually services are exposed through libraries. And a library requires a service if it has a canonical or centralized storage or processing, which can't be done locally.
Now, sure, we do stupid things as services, for sure. But people often do it to monetize the service, or control the users. And so that'll never change.
I recall someone making an "async HTML5 AJAX blink service" as a joke for this trend.
Unfortunately, the obvious economic incentive to effectively paywalling code through the Internet, is so strong that on this very website (that was supposed to have hacker mentality) most of the comments are in favour of it, because “you have to make a living”…
If you have any feasible ideas that could plausibly work where people don't "have to make a living" so that we can find ourselves and not be lost, we're all ears. Until then we're stuck in a place where people need money to pay for frivolous things like "food" or "rent" or "transportation". Still, in the face of that, there's a site called GitHub where people do freely share code, despite the economic misincentives, so I think the kids will be alright.
Of course the prevalent reason to make a service is that it can be monetized to Hell and back again. Does it fit the customer's needs? Who cares, as long as they have no clue what they will end up paying until it is too late to migrate away.
Meh. Yes that happens and yes it can be frustrating. But…
If users want something with long-lasting support and enhancements, that’s going to take work. The people who do this work are going to need to earn a living.
There are lots of ways to accomplish this. Advertising, altruism, one time purchases. But charging for a service can be a perfectly reasonable way of making it sustainable.
I take "where possible" to mean that what you describe should be considered an exception instead of a rule, which I agree with. I do have some thin services which are libraries, but above and beyond directly importing libraries is preferred. With edge functions becoming more popular, this also seems to be the preferred pattern, having "fat" edge functions with shared code, vs many small edge functions calling each other.
> You write a library, then wrap a thin service interface around it. Distribute the lib as needed. Publish the service as needed.
At $CURJOB, we did this but at a higher level of abstraction (an authentication architectural component, rather than a library). I think this is what the author means when they say "writing a standalone server reached through a network protocol".
We see a lot of folks who like the flexibility of consuming functionality as a service or library, as they see fit. We've even had customers who said "we chose you because now we want you as a service, but later will want you as library" or vice versa.
Flexibility isn't free, though. Versioning, support, backwards compatibility (features and performance), even offering the service all become more complex.
You can also start calling your library - a "distributable, embedded, natively consumable micro-service" and then other folks start using it. (Believe it or not - this is what some teams in my org started calling a traditional library)
The library can be re-used in other apps or services.
Then the whole damn library is unit-testable with any arguments you throw at it. Got an OS where argv may be null? You can unit test that. Got a user who decided to use --iamstupid instead of --iamawesome? You can unit test that too. Want to set up environment variables? Well that's not thread safe, but your test harness can do it before it instantiates the library object.
Want to use semver? You can. I use git commit checksums for versions and automatic tagging to semver. It's more annoying but superawesome.
CI has a step with a bash script which takes the most-recent tag, bumps it to a new semver, and pushes the updated tag pointing to the commit going through CI.
We run unit tests with ASAN. Good test coverage gives us good confidence in safety.
I recently picked up a subscription to undo.io and I figure the next time I see any problem then I'll take that for a spin. I've seen trouble with gRPC and trying to debug it is infuriating.
I just used AMQP routing to handle loads, and small limited-run programs that cache credentials like netflix biological inspired systems. We were never convinced gRPC could efficiently handle the periodic traffic spike-nature of our data streams (more of n! edges in fault tolerant mode concern). i.e. we run our intake like an insect colony to handle the various architecture roles, and each process instance is only handling a few network links at a time (i.e. gets rid of threading cleanup, busy credential store hits, and error routing.)
I wish they used Erlang/Elixir/Phoenix channels to reduce the system complexity,
Used to do this in .NET for personal projects then got pulled into various dotnet projects that had their own approaches. But it was really nice. What I had hoped to eventually achieve is being able to hotswap the library somehow so I can have a server with zero downtime for updates, but never found the time to do so. I would assume I would do it via a microservice type of architecture instead.
You can just do this with nginx or whatever you have in front (IIS if you're into Microsoft stuff I guess?). Run service at port xxx1, this is your "live" port. When you wanna upgrade, launch service to port xxx2, do graceful reload of config, swap the ports around so "live" port now points to right service, graceful reload of config again and done!
In .NET for plugin management there are unloadable assemblies so you can dynamically load and unload e.g. plugins at runtime. However, it requires care, has caveats and the industry generally gravitates to other techniques for zero-downtime and/or rolling deployments where the replicas (nodes) are at first drained, then shut down, and replaced one by one or in groups but never in a way where there are none to serve the requests.
Containers can run services without most of the overhead they talk about. And as everyone else is mentioning here, a service-oriented architecture is not dependent on any one tech stack, it’s just a way of designing applications.
180 comments
[ 4.2 ms ] story [ 245 ms ] threadThe customer has a cost when they operate a library instead of consume a service, no doubt. They also get more control (no surprise upgrades, availability is their responsibility) and assurances (no worries about the service suddenly being end of lifed).
When you use an outside service, they might break your system at a time of their choosing.
As always, it depends. Questions I'd ask:
* How critical is the software to your application's proper functioning?
* How big is the team?
* Who are your customers and what are customer expectations around your application's proper functioning?
* How often does the library change?
* What expectations does a service set around backwards compatibility? What commitments are made?
Well sure, but if this is important to you, you might consider a contract with the company that lays out cases and conditions where breakage is acceptable.
Thanks!
That's more or less a description of SQL.
A clean room implementation would likely yield different results but there appears to be some appetite for a solution.
So, until some years ago there was complete nonsense and anarchy in Linux networking management. That is until we got the "ip" program. There's still nonsense and anarchy, because the "ip" program doesn't cover everything, but it's on the right track to organize everything Linux knows about networking under one roof. So, vendors today, like, say, Melanox (i.e. NVidia) choose to interface with "ip" and work with that stack rather than invent their own interfaces.
When it's extendable in predictable and convenient ways, user will extend and enrich functionality.
Now, compare this to Linux storage... I want to scream and kill somebody every time I have to deal with any aspect of it because of how poorly mismanaged it is. There's no uniformity, plenty of standards where at most one is necessary, duplication upon duplication, layers... well, forget layers. Like, say, you wanted a RAID0, well, you have MD RAIDs, you have LVM RAIDs, you have ZFS RAIDs, you have multipassing with DM (is that a RAID, well sorta' depends on what you expected...) also, well, Ceph RDB are also kind of like RAIDs, DRBD can also sort of be like a RAID...
Do you maybe also want snapshots? How about encryption? -- Every solution will end up so particularly tailored to the needs of your organization that even an experienced admin in this very area your org is specializing will have no clue what's going on with your storage.
Needs can be studied, understood, catalogued, rolled into some sort of a hierarchy or some other structure amenable to management. We haven't solved this problem. But we have an even bigger one: no coordination and no desire to coordinate even within Linux core components, forget third-party vendors.
Personally, I am working on a modern Python ORM for PostgreSQL and PostgreSQL alone.
For the comparison being referenced here, if you want to compare RAM, the storage medium that backs compute, to modern persistent storage, here it is:
* 40 GB/s per DIMM vs 5-10 GB/s per NVMe SSD. At most one order of magnitude off, but you can pack enough disks into a computer that the throughput ratio is almost 1:1. AWS EBS is about 1 order of magnitude different here, and that is with network-attached storage.
* 100-200 ns latency (RAM) vs 10-50 us (fast SSD) - about 2 orders of magnitude, but also possible to hide with batching.
K8s PersistentVolume is another decent shot at storage abstraction, only a bit raw.
Finally, more and more tools expect you to have a Postgres they can plug into as backend.
All above assumes you want to treat the library data as a big unknown blob. Once data start being corrupted and need bespoke repair, things are less fun. Access and retention is another fun rabbit hole.
Data is complicated.
If the library is designed to send sql to another storage library...
zeromq is a library
That’s all the storage you need
The library uses only the interface to work with whatever orm/db connector exists in the client project.
If services at any given company all use a standard db library, it could even directly interface assuming your using that. I don't think we're talking about public apis and packages here.
We just have to get a little more fed up with all of these services and then the initial cost of setting it up in the first place will be worth it. Any day now...
For example, it's important that I can add this comment, and I can't delete your comment, but the moderators can. The "storage" software would have to know something about the business logic of a web forum to make that happen.
If I want to leave a comment on an article and then delete it, I can publish the comment in my pod, and I can also publish the deletion. If you've got your browser in a mode where it's interested in my comments, it can pull in the data from my pod and render it in context with the article--whether or not the article's author cared to provide a comments section.
If you drop the idea that anyone is authoritative about how it all comes together on the viewer's screen, you can also dispense with the headaches of being that authority (e.g. services).
No, not all services come connected with a database. Alternatively, often times a database is an artifact of tenancy and the need to manage users which would not be needed, had the functionality be exposed as a library.
More importantly, whether users realize this or not, a library is more beneficial for them than a service in majority of cases. Much in the same way how it's almost always better to own something than to rent it.
Just to give some examples of the above: all the Internet of crap stuff, all sorts of "smart" home nonsense which requires that you subscribe to a service, install an app on your phone and send all your private data unsupervised to some shady Joe Shmo who you know nothing about. To be more specific, take something like Google Nest thermostat. There's no reason this contraption should ever go on the Internet, nor should it require to know your street address, nor your email etc. In fact, the utility it brings is very marginal (saves you few steps you'd have to make to reach for the boiler's controls to program it). It absolutely could've been designed in such a way that it doesn't connect to the Internet, or, at least, to never leave the local area network, and yet it's a cloud service...
In my experience, no, it isn't any common. But it is somewhat common for people to ignore the obvious option and go for services anyway.
Is it something that relies on a private database, queue, massive processing, dedicated hardware, shared state, something geographically distributed? It's a service out of necessity.
Or is it just a bundle of quickly executing code? Then it's obviously a library.
I've never seen anybody try to turn leftPad() into a service.
More software needs iron clad leadership and control. Any organization that lacks this can’t help but produce shit software. There has to be a single person with real decision making power than can force upgrades and prioritization of work, and they need to be able to axe people or teams who can’t hack it.
It seems that worse is better.
What we see is just another manifestation of the current economic paradigm. Waste is winning.
Making too many pairs of jeans so we burn tons of perfectly good new pairs daily wins.
Throwing hundreds of engineers on problems bashing out hundreds of thousands of lines of code sending megabyte messages between dozens of service instances in multiple kubernetes clusters appear to be a winning move. Otherwise someone would beat them, right?
It's less obvious for internal systems and architecture. For example, your company wants to add domain-specific auditing to all of your existing services. You could have every service add a library dependency that lets them just call `auditor.log(...)` and the library internally writes to storage. Or you could add an auditing service with a full HTTP/GRPC API. Or you could go halfway and build an auditing service but provide a library that acts as an interface.
There's no right answer for this IMO, all those approaches have pros and cons.
Of course users want libraries but vendors want services. There are plenty of examples where something could've been a library, if the vendor had user's interest at heart, but instead it's sold (or rather rented out) as a service.
Go to Amazon marketplace, for example. Virtually everything there is a product that should've been a library but is sold as a service...
I still don't get what advantage exactly you are expecting.
Scaling it is only ever a problem for the service; you don't even have to think about it in a library. You can get telemetry from a library just as well as from a service; your users may find that a bit invasive, but it's still way less invasive than calling your service. And you can replace the internals of whatever, without having to worry about who will get affected, that's what defines internals.
I don't see any inherent reason why it should be easier to change the behavior of a service rather than a library.
It's an ownership and control issue.
I prefer those people.
https://www.gnu.org/philosophy/who-does-that-server-really-s...
I'm not going to run my own mail server in 2023, etc.
But many do. It’s still not that hard.
And Polylith for better reuse across projects generally.
this sounds very nice on paper (it sounds common sense trivial utopia that everyone sets out to do anyway, but somehow life, entropy and deadlines get in the way), but ... is there a bigger real life project using something like this?
Ugh, I can't believe I had to read your comment this morning. smh
Please, if you could share more about that, I'd be happy to share the pain
You are now sacrificing the money saved by not hosting to maintain what will likely be a growing matrix of possible versions, underlying assumptions, and my personal favorite: weird customer deployment scenarios that break your library functionality. Bonus points if you are developing in a language without strong typing, or need to integrate with a dependency manager.
Plus, if there was anything about your service that leveraged unique algorithmic improvements or some other proprietary tech, it is now at the mercy of anyone with a decompiler and sufficient time.
I suspect the author may change their mind if exposed to scaled up solutions and technologies.
"But this assumes that slow-to-upgrade users can have negative effects on everyone else. If one user can't have a negative impact on other users, then you don't care if some users are slow to upgrade; they're only hurting themselves."
There's still the support issue, I agree. If a customer paid you money and they are on version n-10, they still expect support.
> Plus, if there was anything about your service that leveraged unique algorithmic improvements or some other proprietary tech, it is now at the mercy of anyone with a decompiler and sufficient time.
This is a valid point. My answer would be: it's all tradeoffs, but if your secret sauce is so valuable that it would be worth decompiling and can't be protected with decent pricing, license terms and lawyers, run a service.
There are plenty of technologies that are not worth decompiling for your average business customers.
Quite logical though if the secret is that your secret sauce is bland and off-flavor copy of a canned soup. Which it usually is.
How is that different with services? I don't develop services, but I can imagine that before breaking the services, you have to poll with your biggest customers and you don't break until they are ready to move to the new version. The alternative I can imagine is to keep providing the old services with a grace period (e.g. /v1 will be available until dec next year).
What am I missing?
With a library, you end up with many different minor or point versions running without control over it.
Then the thick client is controlled server side by the devs.
If you design the thin and thick client intelligently, you can make a lot of changes to the system by merely modifying the thick client (which you control), without needing to update the thin client.
Problems tends to come in a few flavors, namely that this increases complexity and that some changes will always require modifying the thin client.
The biggest pitfall, though, is that you have to actually put thought into the thin client design. “Thin” is an emergent property of a well thought out client. You can’t just “touch thin_client.java” and think because you named it “thin” that it’s inherently decoupled from the service. You have to actually put thought into it.
Your example is exactly right. The service can support either v1 or v2 depending on the version of the thin client.
The main other technique is to have the thin client communicate in a relatively abstract and generic manner. For instead a “write()” endpoint that accepts an “options” dictionary.
“write()” is so generic that it probably won’t have to change, and changes in behavior can be modified by shoving stuff into dict, perhaps with a “version” field to instruct the server how to interpret the call.
When you add a new feature, you can bump the thin client to v2 and shove new options in the dict. Then the server can support v1 and v2. But also, you can modify the server to handle v1 differently. E.g the absence of a “disable_new_feature” key automatically opts v1 callers into the new feature.
I think the details are are really coupled into what you are engineering and what you want to roll out. There’s no magic bullet, and this increases the complexity of your code. Good engineering in my opinion is deciding when these approaches are worth it and how they should be implemented.
As per the article, the advantage of a library over a service is that you don't have the burden of maintaining the said service. How having a thin client and a server helps?
I also don't see how "shoving new options in the dict" can help. From my POV, if the client needs to be updated to benefit from the new features, then there is no way around it, work has to be done. From there, I much prefer having sensible name, parameters, etc. from a library that can be leveraged by static typing rather than a documentation for a JSON API (I know API-as-specs exist but I've never used, maybe that's why I think that).
But the point is they don't have to update even if there's changes. They can update at their leisure.
I understand it may be different if the software is distributed through the package manager of linux distribution because you have to follow what's provided by the distribution. But for a commercial software you can probably bundle your own libraries in executable or compile it statically.
But I am not familiar with the difficulties of the above process so I am probably missing something.
The fact that I don't need to recompile everything whenever libcurl or libssl has a security fix proves otherwise.
https://wiki.openssl.org/index.php/Versioning
Given how frequently needless breaking changes are made, or features are removed and paywalled, I would consider this a feature rather than a bug. Sometimes I have higher priorities than working to support someone else's breaking changes.
Two years ago, I was using an official library for interfacing with a video chat service, and they decided to break the underlying API without updating their library, so I had to rewrite the library myself.
You are wrong by pushing your changes on "slow to update" customers. Customers know when to update much better than you do. If you are trying to update before they want to, you are doing them a disservice.
This attitude is inspired by the desire to sell more, and often times, it means to sell more unwanted crap, where customers are trapped by the "package deal", where they are either forced to update to gain useless features and the headache resulted from various inconsistencies and defects coming from the last update, or they are unable to get the product at all (because the provider cancels support or wouldn't sell older versions etc.)
In other words, you with straight face describe some really shady practice and you don't even realize how bad it sounds.
Selling more is how service providers are able to sell their generic service at a lower price than the cost you would incur by building it in-house. In other words, being forced to upgrade to accommodate features that other users want is the price you pay for sharing the development cost with those other users. That's not shady.
I invite you to review roll outs of security updates. These are rarely about pushing new features, and may have nothing to do with my own code at all. It might just be a version bump to my dependencies config (if a dep manager with shared libraries is involved), or just a refresh of my flatpak equivalent. But, either way, shit breaks in weird and wonderful ways, and there is little I can do but wave my arms frantically at customers.
Imagine a scenario, your service is a low traffic, but service which embeds you as a library is a high traffic with many instances and always opens DB connection.
Why should you optimize your Database for high traffic use case, when your use case is really a low traffic?
And then repeat this for 10 other libraries and library owners. Everyone is optimizing for nothing.
make it even more difficult, 100 different types of services with different traffic patterns are embedding your library with different behaviours when it comes to managing DB connection state
Scenario: your service is accessing a service which exposes PII data and you only process them.
Service which embeds your service enabled audit logs of network requests and made it visible to everyone in the company. You have created a risk unintentionally
Problem with libraries are that they tie the user to a specific language/OS. A cli tool eating and spitting ASCII/UTF-8 is about as cross platform as it gets.
Now, sure, we do stupid things as services, for sure. But people often do it to monetize the service, or control the users. And so that'll never change.
I recall someone making an "async HTML5 AJAX blink service" as a joke for this trend.
I wholeheartedly agree with your sentiment.
Unfortunately, the obvious economic incentive to effectively paywalling code through the Internet, is so strong that on this very website (that was supposed to have hacker mentality) most of the comments are in favour of it, because “you have to make a living”…
Yes, I think we are lost.
If users want something with long-lasting support and enhancements, that’s going to take work. The people who do this work are going to need to earn a living.
There are lots of ways to accomplish this. Advertising, altruism, one time purchases. But charging for a service can be a perfectly reasonable way of making it sustainable.
Maintain the library, modifying the service on as it is affected.
So this effectively comes down to “write a library” as tfa suggests. But there’s no reason the library can’t then be the core of a service.
At $CURJOB, we did this but at a higher level of abstraction (an authentication architectural component, rather than a library). I think this is what the author means when they say "writing a standalone server reached through a network protocol".
We see a lot of folks who like the flexibility of consuming functionality as a service or library, as they see fit. We've even had customers who said "we chose you because now we want you as a service, but later will want you as library" or vice versa.
Flexibility isn't free, though. Versioning, support, backwards compatibility (features and performance), even offering the service all become more complex.
``` #include "servicelib.hpp" int main(int argc, char argv) { return servicelib{argc, argv}.run(); } ```
The library can be re-used in other apps or services.
Then the whole damn library is unit-testable with any arguments you throw at it. Got an OS where argv may be null? You can unit test that. Got a user who decided to use --iamstupid instead of --iamawesome? You can unit test that too. Want to set up environment variables? Well that's not thread safe, but your test harness can do it before it instantiates the library object.
Want to use semver? You can. I use git commit checksums for versions and automatic tagging to semver. It's more annoying but superawesome.
However, ensuring thread safety can sometimes be a challenge. =)
I recently picked up a subscription to undo.io and I figure the next time I see any problem then I'll take that for a spin. I've seen trouble with gRPC and trying to debug it is infuriating.
I wish they used Erlang/Elixir/Phoenix channels to reduce the system complexity,
https://dev.to/codecast/how-to-use-phoenix-channels-18k9
Sometimes we just need to keep a system running, and quietly replace it with version 2 later... Yet later usually never arrives... lol =)
That said, the lib first approach was made a goal in clojure land, and sometimes it adds some choice/integration fatigue.