今日已更新 96 条资讯 | 累计 24053 条内容
关于我们

标签:#Architecture

找到 438 篇相关文章

AI 资讯

Why Are We Still Writing Code When No One Is Going to Read It?

One of the oldest axioms of software engineering is that code is written primarily for humans to read, and only secondarily for machines to execute. Clean code, expressive variable names, and architectural elegance all serve a single purpose: to ensure that the next developer - or our six-months-older self - can understand what on earth happened. Code has always been a cultural artifact, a shared language, a bridge between human intent and silicon. But what happens to this bridge in the era of vibe coding? We are rapidly moving toward a reality where code is generated by LLMs and pull requests are reviewed by LLMs. When the resulting string of characters is spawned by a machine and audited by a machine, human readability immediately ceases to be a primary metric of quality. This forces a radical question upon us: If code no longer needs to be human, does the code itself need to change? Why do we still cling to Python, to micro-frontends, or to neatly structured repositories? We invented these structures to accommodate the cognitive limitations of the human brain - to keep ourselves from drowning in complexity. An AI doesn’t need these training wheels. To an LLM, a 50,000-line monolithic spaghetti-code mess, completely impenetrable to a human eye, is just as easy to parse as the most pristine clean architecture. So, what is the point of the code itself? Is it possible that code is merely a transitional, obsolete interface—a form of "digital carbon monoxide" that we will soon phase out entirely, replacing it with pure intent and mathematical weights? I ran this exact thought experiment in practice recently when I built a tiny, native macOS utility called Portia over a single weekend (you can check it out here: getportia.app ). The app's function is dead simple: when a port gets stuck (EADDRINUSE), it frees it up with a single click. Throughout development, I was almost exclusively in vibe coding mode. I wasn’t thinking about syntax; I was thinking about the problem an

2026-06-18 原文 →
AI 资讯

HLD Fundamentals #4: How Systems Scale: From 0 to 100 Million Users

One of the most common system design interview questions is: "How would you scale a web application from 100 users to 100 million users?" The answer is rarely a single technology. Instead, systems evolve through multiple stages, with each stage solving a specific bottleneck. This article walks through the typical evolution of a scalable system and explains why , how , and when each component is introduced. 1. Single Server Why Start Here? Every application starts simple. In the beginning: Traffic is low Development speed matters more than scalability Infrastructure costs should be minimal What Is It? A single machine handles everything: Frontend Backend Database Users | v Single Server ├── Application └── Database How Does It Work? User sends request. Application processes request. Database stores and retrieves data. Response is returned. Everything happens on one machine. Problem As traffic grows: CPU becomes overloaded Memory becomes insufficient Database competes with application for resources A single server becomes a bottleneck. Interview One-Liner A single server architecture is simple and cost-effective but becomes a bottleneck as traffic and resource usage increase. 2. Application and Database Separation Why Do We Need It? The application and database have different workloads. Application Server: Uses CPU Handles business logic Database Server: Uses memory and storage Handles queries Keeping them together causes resource contention. How Does It Work? Move the database to a separate machine. Users | v Application Server | v Database Server Benefits Independent scaling Better resource utilization Improved performance Example Suppose an e-commerce website receives thousands of requests. The application handles: Authentication Order processing API responses The database handles: Product data Orders User information Separating them prevents one workload from affecting the other. Interview One-Liner Separating the application and database allows each layer to scal

2026-06-18 原文 →
AI 资讯

Cache Stale Data Issues

Originally published on lavkesh.com I recall one of the first design decisions for our payments platform, which was to deploy an in-memory cache for low-latency access to customer account balances. The architecture diagrams looked clean, with Go services using sync.Once-initialized maps in memory, bypassing the database for sub-millisecond reads. However, this approach worked as expected for only three months, until users started reporting inconsistent charges on receipts. The problem surfaced at peak hours when concurrent updates to the same balance would overwrite each other. For instance, the account balance for user ID 12345 went from $1,200 to $850 to $1,200 again within seconds, leaving the cache in a state that defied the database of record. Engineers stared at the logs, baffled by the mismatch between transactions and cached values, because the team had not accounted for the fact that memory maps are not thread-safe by default in Go. Debugging revealed the fundamental error: we were optimizing for speed without considering write-through guarantees. The cache treated concurrent requests as idempotent, which they were not. During a single user’s purchase flow, multiple goroutines could validate the balance, each reading a stale value from memory before any had a chance to commit updates. We had to shift to Redis with explicit lock keys and time-to-live settings, adding 4 milliseconds of latency but ensuring atomicity. The cache also invalidated itself only when a change occurred, not when an upstream source updated. We discovered this when the accounting team reconciled overnight and adjusted balances based on fee settlements - the cache never reflected these updates until it expired naturally. To fix this, we had to implement message queues to broadcast invalidation events across all services. What started as a performance optimization became three nights’ worth of rewriting concurrency models. This experience taught me two concrete lessons about distributed

2026-06-18 原文 →
AI 资讯

HLD Fundamentals #3: Microservices Design Patterns: Strangler, Saga, and CQRS

When organizations scale, a simple monolithic architecture often becomes difficult to maintain, deploy, and scale. This is where microservices come into the picture. However, moving to microservices introduces new challenges: How do we migrate from a monolith safely? How do we handle transactions across multiple services? How do we scale read-heavy applications efficiently? Three popular patterns solve these problems: Strangler Pattern – Monolith to Microservices Migration Saga Pattern – Distributed Transaction Management CQRS (Command Query Responsibility Segregation) – Read/Write Scalability 1. Strangler Pattern Why Do We Need It? Most companies cannot shut down a production monolith and rewrite everything from scratch. A complete rewrite is risky because: Development takes a long time. Existing customers are affected. Bugs can impact business operations. Rollback becomes difficult. The Strangler Pattern allows teams to migrate gradually with minimal risk. What Is It? The Strangler Pattern is a migration strategy where new microservices slowly replace parts of a monolithic application until the monolith is no longer needed. The name comes from the strangler fig tree, which gradually grows around another tree and eventually replaces it. How Does It Work? [Insert diagram here showing Client → API Gateway → Monolith + Microservices] Step 1 All requests go to the monolith. Client | v Monolith Step 2 Introduce an API Gateway (or Controller). Client | v API Gateway | v Monolith Step 3 Extract one module into a microservice. Client | v API Gateway |------> Order Service | v Monolith Step 4 Gradually move more modules. Client | v API Gateway |------> Order Service |------> Payment Service |------> Inventory Service | v Monolith Step 5 Eventually remove the monolith completely. Example Consider an e-commerce application. Initially, everything exists inside one application: Monolith ├── Orders ├── Payments ├── Inventory └── Users Over time: Orders become Order Service Payme

2026-06-18 原文 →
AI 资讯

[System Design] Part 4 — Amazon CONDOR & Anticipatory Shipping

Amazon Fulfillment: The Three Tiers of Optimization Amazon processes billions of orders annually through a network of over 175 fulfillment centers globally. To maintain their 1-2 day (or same-day) delivery guarantees, they built a 3-tier optimization architecture: ┌─────────────────────────────────────────────────────────────┐ │ TIER 1: ANTICIPATORY SHIPPING (Long-term — weeks/months) │ │ → ML predicts demand → Moves inventory close to customers │ │ BEFORE they place an order │ ├─────────────────────────────────────────────────────────────┤ │ TIER 2: REGIONALIZATION (Medium-term — days/weeks) │ │ → Partitions the fulfillment network into autonomous zones│ │ → Ensures 70-80% of orders are fulfilled intra-region │ ├─────────────────────────────────────────────────────────────┤ │ TIER 3: CONDOR (Short-term — hours) │ │ → Continuously re-optimizes the fulfillment plan within │ │ a 5-6 hour window before pick-and-pack begins. │ └─────────────────────────────────────────────────────────────┘ Anticipatory Shipping — Shipping Before You Buy A Crazy but Effective Idea Amazon holds a patent (US Patent 8,615,473) describing a system that begins shipping items BEFORE a customer places an order . It sounds like science fiction, but it's a reality. Traditional Model: Customer orders → Warehouse processes → Ships → Delivered (2-5 days) Anticipatory Shipping: ML predicts: "Customers in Region X will buy 200 iPhone 16s in the next 3 days" → Amazon ships 200 iPhones from a central hub to local delivery hubs in Region X → Customer places order → The item is already locally staged → Delivered same-day! ML Model Input Features Input Feature Significance Purchase history What do they buy, and how often? Browsing behavior What are they looking at? Cart abandonment? Wishlists Explicitly desired items Seasonal patterns Winter coats in November, sunscreen in June Regional demographics High-income areas? Young families? College towns? Trending products Items going viral on social media Weathe

2026-06-18 原文 →
AI 资讯

Mastering Design Principles: Dependency Inversion in Kotlin

Abstract In modern software engineering, writing code that simply "works" is only the first step. The real challenge lies in designing systems that are maintainable, scalable, and easy to test. This article explores the Dependency Inversion Principle (DIP), the final pillar of the SOLID design principles. Through a practical, real-world example in Kotlin, we will demonstrate how to transition from a tightly coupled architecture to an abstraction-based design. This shift dramatically improves our codebase, facilitates unit testing, and prepares our applications for future growth. Introduction: The Chaos of Coupling As applications grow, it is common to see how a minor change in a database schema or a third-party API triggers a domino effect, breaking unrelated parts of the system. This fragility is a direct consequence of tight coupling. Software design principles, particularly SOLID, were established to prevent this architectural decay. Today, we focus on the "D" in SOLID: the Dependency Inversion Principle (DIP). This principle establishes two core rules: High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces). Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions. The Scenario: An E-commerce Payment Processor Imagine you are building the billing system for an online store. To process purchases, the system needs to connect to a payment gateway, such as PayPal. The Bad Way: Tight Coupling (Violating DIP) In this initial design, our high-level business logic (OrderProcessor) directly instantiates and depends on the concrete low-level class (PayPalService). // Low-level component (Concrete detail) class PayPalService { fun executePayment(amount: Double) { println("Processing payment of $$amount via PayPal API.") } } // High-level component (Business logic) class OrderProcessor { // Tight coupling: this class depends directly on a concrete implementation private val

2026-06-18 原文 →
AI 资讯

Saga Orchestration in Go: Distributed Workflows That Actually Roll Back

Every non-trivial business operation touches more than one system. An e-commerce order reserves inventory, charges a payment method, and schedules a shipment — three services, three databases. A bank transfer debits one account and credits another across two ledgers that may not even be in the same data center. A cloud VM provisioning workflow reserves a network port, allocates storage, starts the hypervisor, registers billing, and sends a notification — five services, five independent state stores. The question is: what happens when step four fails after steps one through three have already succeeded? In a monolith backed by a single database, the answer is simple: roll back the transaction. The database engine guarantees atomicity; either everything commits or nothing does. But when your workflow spans multiple services, each owning its own storage, there is no transaction boundary that wraps them all. There is no rollback button. Step one through three have already made durable changes to systems that do not know about each other, and step four's failure has left the system in an inconsistent state. This is not a pathological edge case. It is the default condition in any distributed architecture. And it gets worse: the failure might not be a hard error. The network might time out. The billing service might return a 503. You do not know whether step four applied its effect or not — you only know you did not receive a success response. Now what? This is the problem sagas were designed for. Client Inventory Svc Payment Svc Shipping Svc │ │ │ │ 1 │──reserve(item)──►│ │ │ │◄──── 200 OK ─────│ │ │ │ [reserved ✓] │ │ │ │ │ │ 2 │──────────── charge(card, $99) ────►│ │ │◄───────────────── 200 OK ──────────│ │ │ │ [charged ✓] │ │ │ │ │ 3 │─────────────────────── schedule(order) ─────────────►│ │◄─────────────────────────── 503 ──────────────────── │ │ │ │ [no record ✗] │ │ │ │ ╔══════════════════════════════════════════════════════╗ ║ ⚠ Inconsistent state ║ ║ Inventory: it

2026-06-18 原文 →
AI 资讯

Your Nouns Are Not Your Architecture

A common way to design an application is to begin with its nouns: User Product Order Payment Then each noun receives the standard architectural starter pack: UserController UserService UserRepository The controller receives users, the service services them, and the repository stores them somewhere responsible. This is noun-oriented architecture : treating every important thing in the domain as if it were automatically a useful software boundary. It works for simple CRUD systems. Unfortunately, most applications eventually do something. The noun becomes a drawer Consider a typical UserService : register() findByEmail() resetPassword() changeAddress() disableAccount() mergeAccounts() assignRole() calculateDiscount() These operations all involve a user. That is approximately where their similarity ends. They have different rules, dependencies, side effects, security concerns, owners, and reasons to change. They live together because User was the nearest available noun when the folders were created. As more behaviour accumulates, UserService becomes the official location for anything vaguely user-shaped. Other components depend on it. It gradually depends on authentication, email, permissions, billing, auditing, and several services added during incidents nobody wishes to revisit. The noun becomes both a dependency of everything and a consumer of everything. The folder remains impressively tidy. Name the capability, not the material A better starting question is not: What things exist in this system? It is: What must this system be capable of doing? That leads to components such as: UserRegistrar PasswordResetter AccountMerger OrderPlacer PaymentRefunder SubscriptionCanceller These are agentive names . They name the component responsible for performing a capability. Compare: UserService with: PasswordResetter UserService tells us which noun is nearby. PasswordResetter tells us what the component is for. That difference produces better architectural questions: What rules

2026-06-18 原文 →
AI 资讯

Rate Limiting and Circuit Breakers in Distributed AI Systems

Rate Limiting and Circuit Breakers in Distributed AI Systems Distributed AI systems are inherently complex, handling massive volumes of requests, variable latency from model inference, and dependencies on external services like GPU clusters, databases, or third-party APIs. Without proper safeguards, a single misbehaving component or a sudden traffic surge can cascade into system-wide failure. Two fundamental patterns— rate limiting and circuit breakers —provide essential protection. This post explores their roles, implementation strategies, and practical Python examples tailored for AI workloads. Why Distributed AI Systems Need These Patterns Consider a typical AI pipeline: a user sends a prompt, which hits a load balancer, then an API gateway, then an inference service (e.g., a large language model), which may call a vector database or a fine-tuning API. Each component has capacity limits: GPU inference servers can handle limited concurrent requests. External APIs (e.g., OpenAI, HuggingFace) impose rate limits. Database connections are finite. Without rate limiting, a single abusive client can exhaust resources. Without circuit breakers, a failing downstream service can cause cascading timeouts and resource exhaustion across the entire system. Rate Limiting: Controlling Request Flow Rate limiting restricts how many requests a client, user, or service can make in a given time window. It prevents resource starvation and ensures fair access. Common Algorithms Algorithm Pros Cons Token Bucket Smooth burst handling, easy to implement Memory per bucket Leaky Bucket Constant outflow rate, simple Less flexible for bursts Fixed Window Simple, low overhead Boundary spikes (reset issues) Sliding Window Smoother than fixed, accurate Slightly more complex For AI systems, token bucket is often preferred because it allows short bursts (e.g., a user sending a batch of prompts) while maintaining a long-term average. Python Implementation: Token Bucket Rate Limiter import time impor

2026-06-17 原文 →
AI 资讯

Your Ticket Was Closed. The User Still Couldn't Pay.

Your backend returned 200. The mobile app showed an error. The user tapped "Pay" three times. Three pending charges hit their account. One order was placed. Their balance was short. And your incident log showed zero failures. Every engineer on the team did their job. Nobody solved the problem. This is the most common way engineering teams fail, not through incompetence, but through excellent execution of the wrong unit of work. And until you recognise the difference between completing a task and solving a business problem , you will keep shipping systems that work perfectly and experiences that don't. The Ticket-Thinker vs. The System-Owner Most engineers early in their careers think in tickets. Ticket assigned → code written → tests pass → PR merged → ticket closed. Done. This is fine when you're learning. It's a liability when you're trying to grow. The engineer who closes tickets is useful. The engineer who asks "what problem does this ticket actually solve, and am I solving it in the right place?" that engineer is dangerous in the best way. Here's the distinction in practice. The backend engineer builds a payment endpoint. It processes charges correctly, returns the right status codes, has proper error handling. 100% test coverage. Ticket closed. The mobile engineer builds the payment screen. It calls the endpoint, handles the response, shows confirmation or error. Smooth UI. Ticket closed. The problem nobody owned: what happens when the network drops after the backend processes the charge but before the mobile app receives the confirmation? The backend: charge processed. No error. The mobile: timeout. Shows "Payment failed." User retries. The user: charged twice. Both engineers solved their assigned problem correctly. The business problem — charge the user once and confirm it reliably — went unsolved. Because that problem lived in the space between their tickets, and nobody was watching that space. Real Scenario 1: The Payment That Worked and Failed at the Same

2026-06-17 原文 →
AI 资讯

Two patterns, five services, one n8n workflow

The first two articles in this series each showed one technique. Implementation notes #001 was a dynamic dropdown — a form field that fills itself from an API. Implementation notes #002 was a dynamic credential — an API key that arrives from the form and threads through to the HTTP nodes. This article is the capstone. It walks through all-services-demo , the example workflow that ships with n8n-nodes-ldxhub , where those two techniques combine with a Switch node to host five different AI document-processing services inside one workflow — structured extraction, translation refinement, OCR, PDF conversion, and text extraction. The screenshots and the workflow JSON below come from the n8n-nodes-ldxhub package. The patterns themselves are generic — they work for any set of services you want to consolidate into a single template. This is not a "follow these steps" article. It's a parts catalog. No two readers are solving the same problem, and templates rarely fit anyone's situation as-is. Take what fits. Drop the rest. You don't need to understand all 46 nodes to reuse the patterns. The shape The workflow has 46 nodes — large enough to look intimidating in the editor, but structurally it's just five repeated paths plus a small routing section. The entry section is two nodes: On form submission — the trigger. Asks the user which service they want and collects an API key. Route by Service — a Switch node with five outputs, one per service. Everything to the right of the Switch is service-specific. Five paths fan out: StructFlow, RefineLoop, RenderOCR, CastDoc, ExtractDoc. Each path ends in two Form Ending nodes — one for success (auto-downloads the result), one for error. That's the spine: form → switch → service path → ending. The complexity is pushed into the service paths. The spine: routing by static comparison The Switch node ("Route by Service") uses Rules mode. Each rule reads the same expression from the form — {{ $json.service }} — and compares it to a static serv

2026-06-17 原文 →
AI 资讯

Production RBAC patterns for Go and Node startups

A founder told me last year: "We need admin features shipped by Friday. Can we just hardcode the roles for now?" That was 7 months ago. They're still paying for that decision today. This article is the breakdown I wish I had given them before that Friday — the patterns that separate a quick MVP version of RBAC from one that won't bite you back in six months. If you're a startup CTO, technical founder, or senior engineer about to ship admin/dashboard features for the first time, this is for you. The 4 signs your RBAC is a time bomb I've reviewed this exact pattern in 5+ early-stage codebases. Every one of them eventually hit the wall. Look for these signs: 1. Roles hardcoded in the JWT { "sub" : "user_123" , "role" : "admin" } Simple. Until you need to: Add a new role → migration + token refresh strategy Give one user a temporary elevated permission → "we'll just reissue tokens" Audit who had admin last month → good luck 2. Permission checks scattered across 40+ controllers // In every single handler: if user . Role != "admin" { return c . Status ( 403 ) . JSON ( ... ) } By the time you have 30 endpoints, you've made the same mistake 30 times. Refactoring is a full sprint. 3. One admin superuser that can do everything Until a sales rep accidentally deletes a customer record because they were elevated to admin "just for that one task last week." 4. Zero audit trail When the data goes missing, your only investigative tool is git blame on the source code and a desperate grep through CloudWatch logs. If you've nodded at any of these — keep reading. The real problem: policies-as-code Most teams treat permissions like business logic. They live in source files, gated by if statements, deployed with the rest of the code. This is the original sin. Code changes need deploys. Permissions change daily. When marketing onboards a new role next quarter, when a customer success rep needs view-only access to billing, when legal asks "who could have read this customer's data on Tuesda

2026-06-17 原文 →
AI 资讯

I Stopped Trusting the LLM With the Score: Building an Honest AI Portfolio Reviewer

Ask a language model to score a developer portfolio out of 100 and you get a confident number back. Hand it a near-empty page with a name and a broken avatar, and it will often still tell you something like 92. Nice layout. Strong personal branding. The model is being polite, not accurate. That was the first wall I hit building Leon, the reviewer inside getfolio. If the score is not trustworthy, nothing downstream matters: the critique, the suggestions, and the fix button all hang off a number the model invented to sound encouraging. This is the build log of how I stopped letting the model hold the pen. Short version: a deterministic rules engine owns the score, and the language model only owns the words around it. The failure mode: an LLM judge wants to be liked If you have shipped anything with an LLM evaluator you have probably seen this. You hand it a rubric, a JSON schema, even worked examples, and it still drifts upward. Empty inputs get encouraging scores. Weak inputs get the benefit of the doubt. Strong inputs land in the same band as the weak ones, just with longer praise. A few reasons, roughly in order of how much they hurt: Tuning rewards a helpful, encouraging tone. Harsh scoring reads as unhelpful, so the model softens it. The model has no ground truth for what a 70 versus an 85 looks like in your specific domain. It is scoring on vibes. Scoring and explaining are entangled. The model writes the kind explanation first, then picks a number to match the nice things it just said. Run it twice on the same input and you get two different numbers. There is no anchor. For a portfolio reviewer that real recruiters and developers would act on, that was a non-starter. If Leon says 64, an empty page should not be able to reach 64 by accident, and a strong portfolio should not get talked down to it either. The number has to mean something. The fix: rules engine owns the score, model owns the language The architecture splits responsibilities hard. A deterministic e

2026-06-17 原文 →
AI 资讯

Fire-and-forget AI engineering: letting agents ship a production app unsupervised

"An AI agent just built a production landing page, with GDPR audit logs and encryption baked in. I wasn't even at my desk." That is not a lucky one-shot. It is a repeatable workflow. Piotr Karwatka recorded a full tutorial showing how to go from idea to a production-ready app on Open Mercato - the AI-Engineering Foundation Framework for CRM/ERP - with no babysitting and no ping-pong prompting. This is the technical version: what the loop actually looks like, why it doesn't fall apart, and which patterns you can lift into your own stack. The problem with conversational coding The default AI coding loop is single-threaded and human-bound: prompt -> generate -> you spot a bug -> correct -> re-prompt -> repeat It holds for snippets. It collapses the moment the task touches real architecture - multi-tenancy, RBAC, event flow, encryption, audit logging. Corrections pile up in the context window, the agent loses the thread, and you are back to typing. You are the bottleneck, sitting in the inner loop. The workflow in the tutorial moves you to the outer loop : you review a finished, tested PR instead of every keystroke. goal -> agent: branch + implement + test + open PR -> you: review PR The reason this is even possible on Open Mercato is that the hard architectural decisions are already encoded as conventions, specs and agent-readable skills ( AGENTS.md , task routing, spec skills). The agent is not inventing how RBAC or GDPR logging should work - it reads the foundation and follows it. 1. Fire-and-forget: the autonomous PR loop The execution agent owns the full unit of work: 1. git checkout -b feat/lead-capture-landing 2. implement against framework conventions 3. run the test suite (Playwright integration tests included) 4. open a structured PR: what changed, why, how it was verified You are no longer correcting tokens. The deliverable is a reviewable artifact. In the tutorial the output is concrete: a live site capturing leads straight into the Open Mercato CRM, with GD

2026-06-17 原文 →
AI 资讯

Why AI Agents Make Me Reach for SQLite

Lately I keep reaching for SQLite where, before, I'd have reached for Postgres without thinking. It started with small services, then a bigger question: could a multi-tenant SaaS actually run on SQLite? And for AI agents specifically, isn't a local, embedded database the more natural home for their state? Turso is the version of this stack I've found most compelling so far, especially when paired with Cloudflare. I wish D1 would reach embedded-replica parity with Turso, and that AWS offered a managed SQLite-style service the way it offers RDS for Postgres. This isn't a "Postgres is over" argument. I still use Postgres more often than SQLite. And it isn't advice. It's just where my thinking has drifted recently — written down mostly so I can find out where it's wrong. Read it as one person's notes, not a recommendation. Where I've landed for now (and expect to keep revising): SQLite isn't replacing Postgres. For work state , it's increasingly my first reach, not my last. AI agents push this harder: their state is high-churn, local, and mostly private. The answer isn't all-local. It's a local workbench plus a central ledger . Why the old default existed For years, "where does the data live?" had one practical answer: a server, behind an API, in a shared Postgres. A lot of that wasn't architecture — it was the cheapest shape available. SQLite was already everywhere, but it lacked the operational layer that makes a database viable as SaaS infrastructure: networking, replication, managed backups, and a way to run many small databases without drowning in tooling. So centralizing was the path of least resistance, and a tenant_id column in shared Postgres became the reflex. What changed isn't SQLite. It's that the ecosystem grew the missing parts — and for a growing class of workloads, the thing doing the most frequent writing moved onto my own machine. The constraint that's lifting SQLite itself is, by design: Embedded, not networked — a library, nothing listens on a port.

2026-06-17 原文 →
AI 资讯

Mailboxes as Cattle: Ephemeral Email Infrastructure

When was the last time you deleted an email account on purpose? For most teams the answer is never, and that tells you something. We treat mailboxes the way we treated servers in 2008: hand-built, carefully named, kept alive indefinitely because recreating one is painful. They're pets. Meanwhile every other piece of our infrastructure — compute, queues, databases — became cattle: numbered, provisioned by code, destroyed without sentiment when the job ends. Email is finally catching up. With Nylas Agent Accounts (in beta), a mailbox is created with one call and destroyed with another, and that symmetry is the whole point. The full lifecycle in two commands Provisioning, from the CLI: nylas agent account create signup-agent@agents.yourdomain.com Or via POST /v3/connect/custom with "provider": "nylas" — no OAuth, no refresh token, just an address on a domain you've registered. Teardown is equally unceremonious: nylas agent account delete signup-agent@agents.yourdomain.com --yes (The API equivalent is a DELETE on the grant.) The signup automation recipe treats this as a loop: provision a fresh inbox, point a third-party signup form at it, catch the verification email through a message.created webhook, follow the confirmation link, delete the grant. No human inbox involved at any step, and nothing left behind. The middle of that loop is about twenty lines of webhook handler, and the recipe's version filters hard before acting — right grant, right sender, right URL shape: const { grant_id , id : messageId , from } = event . data . object ; if ( grant_id !== AGENT_GRANT_ID ) return ; const sender = from [ 0 ]?. email ?? "" ; if ( ! sender . endsWith ( " @saas-you-care-about.example.com " )) return ; const match = /https: \/\/ saas-you-care-about \. example \. com \/ confirm \? token= [^ " \s < ] +/ . exec ( message . body , ); if ( match ) await fetch ( match [ 0 ]); Nylas fires message.created within a second or two of mail arriving, so the whole signup round-trip typical

2026-06-17 原文 →