![Oracle Drags Its Feet in the JavaScript Trademark Dispute](https://cdn.sanity.io/images/cgdhsj6q/production/919c3b22c24f93884c548d60cbb338e819ff2435-1024x1024.webp?w=400&fit=max&auto=format)
Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Keep your pubsub saucy
This package is a wrapper for guaranteeing exactly-once handling of messages from Google Cloud PubSub.
> npm install a1pubsub
This package requries running in a environment with GCP Application Default Credentials. If you authenticate on your machine using the gcloud
cli, then you're good to go!
Setter needs one-to-many relationships between events that are published and the corresponding event handlers. i.e. We would like to emit an event such as JOB_APPROVED
, and have various services be able to do different things with those events independently.
The way GCP PubSub works is that it delivers messages to every subscriber at least once (docs). Thus, if you have a system that must only process events exactly once, you have to implement idempotence yourself.
This package is intended to abstract away the need to manage idempotence yourself.
What follows is a tldr of the actual GCP documentation located at: https://cloud.google.com/pubsub/docs
Topic:
quote_approved
events, and then a separate stream for job_scheduled
events, etc etcgcloud pubsub topics create myTopic
Subscription:
gcloud pubsub subscriptions create --topic myTopic mySubscriptionName
Let's do a code-a-long...
First, import the PubSub
class from a1pubsub
import { PubSub } from 'a1pubsub'
Let's instantiate the PubSub
class:
const myGcpProjecId = 'setter-develop-82828'
const decodingTable = justHoldYourHorsesForASecond
const ps = new PubSub(myGcpProjecId, decodingTable)
When you instantiate PubSub
, the module will try to authenticate to gcp using Application Default Credentials.
Please take 5 minutes to familiarize yourself with ADC, as this is the only way to authenticate to GCP PubSub for the moment. Regardless, here's what you need to know when developing on your machine:
gcloud
CLI installed on your machine
gcloud
CLI to the corresponding project that you're trying to work with
PubSub
with the 'setter-develop-82828'
project - thus I must be authenticated via gcloud auth
to that project as wellNow that you've instantiated PubSub
, you can now publish events!
await ps.publish(
'quote_approved', // topic name
{ id: 1232, title: 'Window Cleaning', client_first_name: 'Jerry' } // data - must be serializeable to JSON
)
Note that any topic that you publish to must already exist! See the links above for creating topics.
Note For Setter engineers: Create topics sparringly and with good reason. Try to adhere to creating topics that represent events that have occurred. Don't create a topics that represent actions to be done. For more context, refer to this talk.
Ok back to the code along.
So in the above code snippet, you saw that PubSub
was instantiated with a decodingTable
.
Before I get to explaining in plain english, here is the type definition:
type SubscriptionId = string
interface SubscriptionHandler<T extends {}> {
validator: (json: JSON) => T
handler: (data: T) => Promise<boolean>
}
export type DecodingTable<T extends {}> = Map<SubscriptionId, SubscriptionHandler<T>>
In plain english: A decoding table is a Map
whose keys are strings (that represent subscription identifiers), and whose values are SubscriptionHandler
s.
A SubscriptionHandler
is an object with two keys:
validator
: As I mentioned alredy, GCP pubsub data is schemaless. All you know is that the data is serializeable to json.
JSON
type is defined in src/json.ts
and it's just a type-level definition of JSON.parse
.handler
: The actual subscription handler, it takes your validated data and returns a promise with a boolean.
true
: success, any subsequent messages that GCP pubsub might deliver will get ignored
false
: failure, the event will be tracked, but our system will be expecting that same event from being delivered again on a retry
So if your pubsub module needs to handle 5 subscriptions, then your DecodingTable
will have 5 keys, and 5 corresponding SubscriptionHandler
s.
import express from 'express'
import { PubSub } from 'a1pubsub'
import * as Joi from '@hapi/joi'
const app = express()
const port = 3000
const myGcpProjecId = 'setter-develop-82828'
const decodingTable = new Map()
const quoteApprovedSchema = {
quote_id: Joi.number().required()
title: Joi.string().required(),
client_first_name: Joi.string().required(),
}
/*
* pretend a whole bunch of other schemas were defined here
* such as:
* - jobCancelledSchema
* - homeConsultationCompleted
* - purchaseOrderCreated
* - jobCompleted
* - etc etc
*/
decodingTable.set('quote_approved', {
validator: (data) => {
// using joi here ... but you can use anything you want
// runtypes, yum, validatorjs etc etc etc
const { approvedQuoteData, error } = Joi.object(quoteApprovedSchema)
.options({ stripUnknown: true })
.validate(data)
if (approvedQuoteData) {
return approvedQuoteData
} else {
return
}
},
handler: sendQuoteApprovalEmailToClient,
})
decodingTable.set('job_cancelled', {
validator: (data) => {
const { cancelledJobData, error } = Joi.object(jobCancelledSchema)
.options({ stripUnknown: true })
.validate(data)
if (cancelledJobData) {
return cancelledJobData
}
},
handler: refundClient
})
/*
* pretend a whole bunch of SubscriptionId + SubscriptionHandler pairs
* have been added to the decodingTable Map
* to handle all the various subscriptions
*/
const ps = new PubSub(myGcpProjecId, decodingTable)
app.post('/pubsub', async (req, res) => {
const pubsubMessage = req.body
// if error is undefined, then all went well
// the PubSub module will guarantee that duplicate messages
// are not processed again
const error = await ps.handlePubSubMessage(pubsubMessage)
if (error) {
// GCP PubSub will re-enqueue the message and retry at a later point in time
res.sendStatus(422)
} else {
// GCP PubSub will **PROBABLY** not send the same message again
res.sendStatus(204)
}
})
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
Note that by default, your pubsub events are not authenticated. Please ensure that you authenticate events. More info: https://cloud.google.com/pubsub/docs/authentication
Out of the box, idempotency is implemented / managed via an in-memory hash map (using the js Map
). But you can provide your own persistence mechanism so long as it implements the StateManager
interface (link).
Example:
import { PubSub } from 'a1pubsub'
new PubSub('my-project-id', decodingTable, psqlStateManager)
FAQs
GCP PubSub wrapper to add idempotency
The npm package a1pubsub receives a total of 0 weekly downloads. As such, a1pubsub popularity was classified as not popular.
We found that a1pubsub demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.