Slide 1

Slide 1 text

Stripe Is My DNS Provider Nowโ€จ When Bad Ideas Meet Good APIs Paul Conroy / @conroyp

Slide 2

Slide 2 text

From Dublin, Ireland Started playing with the web 30+ years ago (Notepad, Frontpage & Geocities!) CTO at Square1 conroyp.com / @conroyp Paul Conroy ๐Ÿ‘ด ๐ŸŒ ๐Ÿ‡ฎ๐Ÿ‡ช

Slide 3

Slide 3 text

https://www.lastweekinaws.com/blog/route-53-amazons-premier-database/

Slide 4

Slide 4 text

โ— The only AWS service with a public 100% SLA โ— No charges for data transfer anywhere inside of AWS or out to the internet โ— Free if you move between zones every ~12 hours โ— It works! Route 53 as a database?

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Stripe Metadata

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Stripe Metadata โ— Custom key-value pairs you define โ— Available on most core Stripe objects โ— Useful for reconciling distinct systems โ— Visible in the Dashboard โ— Searchable via API # PaymentIntent with custom metadata intent = Stripe::PaymentIntent.create({ amount: 10000, currency: "eur", metadata: { order_id: "A1234", shipping_initiated: โ€œno", } })

Slide 12

Slide 12 text

DNS โ— Translates names into destinations www.stripe.com -> 52.215.231.162 โ— Distributed โ— Hierarchical lookup โ— Fast response times

Slide 13

Slide 13 text

customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", a: "54.76.53.164", cname: "home.example.com", mx: "mail01.google.com", } }) โ— Look up metadata.dns_domain โ— Extract records from metadata โ— Format as DNS response

Slide 14

Slide 14 text

DNS โ— Translates names into destinations www.stripe.com -> 52.215.231.162 โ— Distributed โ— Hierarchical lookup โ— Fast response times

Slide 15

Slide 15 text

DNS โ— Translates names into destinations www.stripe.com -> 52.215.231.162 โ— Distributed โ— Hierarchical lookup โ— Fast response times

Slide 16

Slide 16 text

{ "@": { "A": ["54.76.53.164"] }, "www": { "CNAME": ["stripe.com"] }, "blog": { "CNAME": ["blog.wordpress.com"] }, "_mx": { "MX": [ { "preference": 10, "exchange": "mail01.google.com" } ] } } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", a: "54.76.53.164", cname: "home.example.com", mx: "mail01.google.com", } }) Nested Structure Flat Structure

Slide 17

Slide 17 text

{ "@": { "A": ["54.76.53.164"] }, "www": { "CNAME": ["stripe.com"] }, "blog": { "CNAME": ["blog.wordpress.com"] }, "_mx": { "MX": [ { "preference": 10, "exchange": "mail01.google.com" } ] } } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", a: "54.76.53.164", cname: "home.example.com", mx: "mail01.google.com", } }) Nested Structure Flat Structure

Slide 18

Slide 18 text

Serialization! customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME":["blog.wordpress.com."]},"www":{"CNAME": ["stripe.com"]},"@":{"A":["54.76.53.164"],"MX": [{"priority":10,"exchange":"mail01.google.com."}]}}' } })

Slide 19

Slide 19 text

Serialization! customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME":["blog.wordpress.com."]},"www":{"CNAME": ["stripe.com"]},"@":{"A":["54.76.53.164"],"MX": [{"priority":10,"exchange":"mail01.google.com."}]}}' } })

Slide 20

Slide 20 text

Serialization! customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME":["blog.wordpress.com."]},"www":{"CNAME": ["stripe.com"]},"@":{"A":["54.76.53.164"],"MX": [{"priority":10,"exchange":"mail01.google.com."}]}}' } })

Slide 21

Slide 21 text

customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })

Slide 22

Slide 22 text

const customers = await stripe.customers.search({ query: "metadata['dns_domain']:'${domain}'" }); if (customers.data.length === 0) { console.log(`No customer found for ${domain}`); return null; } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })

Slide 23

Slide 23 text

const customers = await stripe.customers.search({ query: "metadata['dns_domain']:'${domain}'" }); if (customers.data.length === 0) { console.log(`No customer found for ${domain}`); return null; } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })

Slide 24

Slide 24 text

const customers = await stripe.customers.search({ query: "metadata['dns_domain']:'${domain}'" }); if (customers.data.length === 0) { console.log(`No customer found for ${domain}`); return null; } customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })

Slide 25

Slide 25 text

customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })

Slide 26

Slide 26 text

customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })

Slide 27

Slide 27 text

customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ email: "[email protected]", name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } })

Slide 28

Slide 28 text

customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) customer = Stripe::Customer.create({ email: "[email protected]", name: "DNS records for stripe.com", metadata: { dns_domain: "stripe.com", dns_records: '{"blog":{"CNAME...}}' } }) async lookupDnsRecords(domain, subdomain) { const lookupEmail = `dns@${domain}`; const customers = await stripe.customers.list({ email: lookupEmail, limit: 1 }); ...

Slide 29

Slide 29 text

The Server โ— Create a DNS server with dns2 โ— Retrieve record for domain โ—‹ Check if request is for sub-domain โ— JSON-decode metadata โ— Convert to DNS record format https://www.npmjs.com/package/@types/dns2 require('dotenv').config(); const stripe = require('stripe')(process.env.SECRET_KEY); const dns2 = require('dns2'); const { Packet } = dns2; class StripeDnsServer { constructor(options = {}) { this.stripeClient = options.stripeClient || stripe; this.port = options.port || 5333; this.address = options.address || '0.0.0.0'; this.server = this._createServer(); } ...

Slide 30

Slide 30 text

The Server โ— Create a DNS server with dns2 โ— Retrieve record for domain โ—‹ Check if request is for sub-domain โ— JSON-decode metadata โ— Convert to DNS record format https://www.npmjs.com/package/@types/dns2 require('dotenv').config(); const stripe = require('stripe')(process.env.SECRET_KEY); const dns2 = require('dns2'); const { Packet } = dns2; class StripeDnsServer { constructor(options = {}) { this.stripeClient = options.stripeClient || stripe; this.port = options.port || 5333; this.address = options.address || '0.0.0.0'; this.server = this._createServer(); } ... async _handleRequest(request, send) { const response = Pkt.createResponseFromRequest(request); const [question] = request.questions; const { name, type } = question; try { // Check for possible subdomain query const { topDomain, subdomain } = parseDomain(name); // Look up records const recordSet = await lookupDnsRecords( topDomain, subdomain ); ...

Slide 31

Slide 31 text

The Server โ— Create a DNS server with dns2 โ— Retrieve record for domain โ—‹ Check if request is for sub-domain โ— JSON-decode metadata โ— Convert to DNS record format https://www.npmjs.com/package/@types/dns2 require('dotenv').config(); const stripe = require('stripe')(process.env.SECRET_KEY); const dns2 = require('dns2'); const { Packet } = dns2; class StripeDnsServer { constructor(options = {}) { this.stripeClient = options.stripeClient || stripe; this.port = options.port || 5333; this.address = options.address || '0.0.0.0'; this.server = this._createServer(); } ... async _handleRequest(request, send) { const response = Pkt.createResponseFromRequest(request); const [question] = request.questions; const { name, type } = question; try { // Check for possible subdomain query const { topDomain, subdomain } = parseDomain(name); // Look up records const recordSet = await lookupDnsRecords( topDomain, subdomain ); ... async lookupDnsRecords(domain, subdomain) { const email = `dns@${domain}`; const customers = await stripe.customers.list({ limit: 1, email: email }); const dnsRecords = JSON.parse( customers.data[0].metadata.dns_records || '{}' ); // Check for exact match, wildcard if no exact match let recordSet = dnsRecords[subdomain]; if (!recordSet && subdomain !== '@') { recordSet = dnsRecords['*']; } return recordSet || null; }

Slide 32

Slide 32 text

The Server โ— Create a DNS server with dns2 โ— Retrieve record for domain โ—‹ Check if request is for sub-domain โ— JSON-decode metadata โ— Convert to DNS record format https://www.npmjs.com/package/@types/dns2 require('dotenv').config(); const stripe = require('stripe')(process.env.SECRET_KEY); const dns2 = require('dns2'); const { Packet } = dns2; class StripeDnsServer { constructor(options = {}) { this.stripeClient = options.stripeClient || stripe; this.port = options.port || 5333; this.address = options.address || '0.0.0.0'; this.server = this._createServer(); } ... async _handleRequest(request, send) { const response = Pkt.createResponseFromRequest(request); const [question] = request.questions; const { name, type } = question; try { // Check for possible subdomain query const { topDomain, subdomain } = parseDomain(name); // Look up records const recordSet = await lookupDnsRecords( topDomain, subdomain ); ... async lookupDnsRecords(domain, subdomain) { const email = `dns@${domain}`; const customers = await stripe.customers.list({ limit: 1, email: email }); const dnsRecords = JSON.parse( customers.data[0].metadata.dns_records || '{}' ); // Check for exact match, wildcard if no exact match let recordSet = dnsRecords[subdomain]; if (!recordSet && subdomain !== '@') { recordSet = dnsRecords['*']; } return recordSet || null; } addRecordsToResponse(response, name, recordSet, type) { switch (type) { case Packet.TYPE.A: if (recordSet.A) { recordSet.A.forEach(ip => { this.addRecord(response, name, Packet.TYPE.A, { address: ip }); }); return true; } break; case Packet.TYPE.CNAME: if (recordSet.CNAME) { ...

Slide 33

Slide 33 text

๐Ÿง‘๐Ÿ’ป User dig DNS Lookup CLI tool @badidea.conroyp.com DNS Server -p 53 Port to use example.com Domain to check

Slide 34

Slide 34 text

๐Ÿง‘๐Ÿ’ป User

Slide 35

Slide 35 text

๐Ÿง‘๐Ÿ’ป User DNS Server

Slide 36

Slide 36 text

๐Ÿง‘๐Ÿ’ป User DNS Server Stripe

Slide 37

Slide 37 text

๐Ÿง‘๐Ÿ’ป User DNS Server Stripe

Slide 38

Slide 38 text

๐Ÿง‘๐Ÿ’ป User DNS Server Stripe

Slide 39

Slide 39 text

๐Ÿง‘๐Ÿ’ป User DNS Server Stripe

Slide 40

Slide 40 text

Live Demo!

Slide 41

Slide 41 text

Live Demo!

Slide 42

Slide 42 text

It worked! delete this slide quickly if demo failed horribly...

Slide 43

Slide 43 text

It worked! delete this slide quickly if demo failed horribly... โ€ฆ but should it?

Slide 44

Slide 44 text

โ— Slower than standard DNS โ— Incomplete records โ— Not handling ttls โ— Butโ€ฆ It worked! delete this slide quickly if demo failed horribly... โ€ฆ but should it?

Slide 45

Slide 45 text

โ— Slower than standard DNS โ— Incomplete records โ— Not handling ttls โ— Butโ€ฆ It worked! delete this slide quickly if demo failed horribly... โ€ฆ but should it?

Slide 46

Slide 46 text

https://github.com/conroyp/MetadNS/ Introducing metadNS: When Bad Ideas Meet Good APIs

Slide 47

Slide 47 text

โ— Is this a good idea? What exactly is the point?

Slide 48

Slide 48 text

โ— Is this a good idea? What exactly is the point? No.

Slide 49

Slide 49 text

โ— Is this a good idea? What exactly is the point? No. โ— But is it a viable option if youโ€™re in a hurry?

Slide 50

Slide 50 text

โ— Is this a good idea? What exactly is the point? No. โ— But is it a viable option if youโ€™re in a hurry? Also No.

Slide 51

Slide 51 text

โ— Is this a good idea? What exactly is the point? No. โ— But is it a viable option if youโ€™re in a hurry? Also No. โ— But is it ultimately a practical use of this technology?

Slide 52

Slide 52 text

โ— Is this a good idea? What exactly is the point? No. โ— But is it a viable option if youโ€™re in a hurry? Also No. โ— But is it ultimately a practical use of this technology? Absolutely not - it is the jokiest of joke projects.

Slide 53

Slide 53 text

โ— Is this a good idea? What exactly is the point? No. โ— But is it a viable option if youโ€™re in a hurry? Also No. โ— But is it ultimately a practical use of this technology? Absolutely not - it is the jokiest of joke projects.

Slide 54

Slide 54 text

Itโ€™s a silly idea, butโ€ฆ โ— Weโ€™ve learned about metadata in a practical way โ— We hit its storage constraints โ—‹ .. and found workarounds (serialisation) โ— Weโ€™ve worked with the search api โ—‹ .. and read the docs, finally.. ๐Ÿ˜ฌ โ— Practical, hands-on experience to take into our real-world projects

Slide 55

Slide 55 text

customer = Stripe::Customer.create({ email: "[email protected]", metadata: { loyalty_program: "no", } }) # PaymentIntent with custom metadata intent = Stripe::PaymentIntent.create({ amount: 10000, currency: "eur", metadata: { order_id: "A1234", shipping_initiated: โ€œno", } })

Slide 56

Slide 56 text

customer = Stripe::Customer.create({ email: "[email protected]", metadata: { loyalty_program: "no", } }) # PaymentIntent with custom metadata intent = Stripe::PaymentIntent.create({ amount: 10000, currency: "eur", metadata: { order_id: "A1234", shipping_initiated: โ€œno", } }) # Set value if customer spends > 5000 customer = Stripe::Customer.update( '{{CUSTOMER_ID}}', { metadata: { premium_customer: "yes", } } )

Slide 57

Slide 57 text

Not every idea needs a microservice!

Slide 58

Slide 58 text

Stripe Workflows โ— Stripe-hosted โ— Visual Workflow builder โ— Works across many Stripe products https://docs.stripe.com/workflows

Slide 59

Slide 59 text

Stripe Workflows โ— Stripe-hosted โ— Visual Workflow builder โ— Works across many Stripe products https://docs.stripe.com/workflows

Slide 60

Slide 60 text

Stripe Workflows โ— Stripe-hosted โ— Visual Workflow builder โ— Works across many Stripe products https://docs.stripe.com/workflows

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

Developing in an AI age โ— Well-structured APIs โ— Predictable (helpful!) error messages โ— Simple to verify (sandboxes) โ— Current documentation

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

๐Ÿ“ง Email Google Sheets Git

Slide 69

Slide 69 text

๐Ÿ“ง Messaging Queue Google Sheets Git

Slide 70

Slide 70 text

๐Ÿ“ง Messaging Queue Google Sheets Config Store

Slide 71

Slide 71 text

๐Ÿ“ง Messaging Queue Production API & DB Config Store

Slide 72

Slide 72 text

Not every idea needs a microservice!

Slide 73

Slide 73 text

https://www.lastweekinaws.com/blog/route-53-amazons-premier-database/

Slide 74

Slide 74 text

https://www.linkedin.com/posts/aaron-stuyvenberg_aws-finally-gave-a-talk-about-the-oct-20th-activity-7404902608139661312-NwO7/

Slide 75

Slide 75 text

https://www.linkedin.com/posts/aaron-stuyvenberg_aws-finally-gave-a-talk-about-the-oct-20th-activity-7404902608139661312-NwO7/

Slide 76

Slide 76 text

โ— MetadNS Project Codeโ€จ https://github.com/conroyp/metadns โ— Stripe Metadata https://docs.stripe.com/metadata โ— Stripe Workflows https://docs.stripe.com/workflows โ— Route53, Amazonโ€™s Premier Database https://www.lastweekinaws.com/blog/route-53-amazons-premier-database/โ€จ โ— Using Google Sheets as a RESTful API https://www.conroyp.com/articles/using-google-sheets-json-rest-api Further Reading

Slide 77

Slide 77 text

Conroyp.com @conroyp [email protected] ๐ŸŒ ๐ŸŒ ๐Ÿ“ง Thank You!