AI 资讯
Catalog Isolation: Background Removal and Manual Crop Trade-offs Explained
Short answer: use automated background removal for volume, then reserve manual crops for images where a wrong edge costs more than the bandwidth and review time. The useful design is a queue with a confidence gate, not a permanent argument over which tool is “best.” How Should Catalog Teams Balance Background Removal and Manual Crop? Catalog isolation sounds like a visual task. In a SaaS catalog, it is a data pipeline. A seller uploads a product photo, the service produces an isolated asset, and every downstream surface expects the subject to stay inside a predictable box. Quality and bandwidth pull in opposite directions: a high-resolution source preserves fine edges but costs more to move and process; an aggressive resize is quick but can erase the exact detail the mask needs. The decision therefore belongs in a policy that engineering, catalog operations, and support can inspect. Give that policy named outcomes such as auto , manual , and needs_source ; attach the crop rectangle and confidence to each result; and retain enough input metadata to reproduce the decision. Without those records, a quality complaint becomes a debate over screenshots. With them, the team can compare the received file, preview dimensions, mask, final derivative, and policy version in order. Start with an explicit decision table. It gives support and operations one shared vocabulary when an image lands in the review queue. Input or business signal Default path Why Reconsider when Clean background, centered object, thousands of SKUs Automated removal at a bounded preview size Fast throughput and consistent framing Hair, glass, or transparent parts dominate the silhouette Irregular edges or a high-value hero image Manual crop and edge review A person can preserve meaningful contours The review queue becomes the release bottleneck Uncertain subject or cluttered scene Keep the original and request a better photo Prevents a confident-looking bad cutout The seller can supply a controlled backdr
AI 资讯
FastAPI for AI Engineers - Part 8: Uploading Files with FastAPI
In the previous article, we learned how to secure our APIs using JWT Authentication and protect routes from unauthorized access. Now let's explore another feature used in almost every AI application— file uploads . If you've built applications like ChatGPT, document Q&A systems, resume analyzers, legal contract reviewers, or medical report analyzers, one thing is common across all of them: The user uploads a file. Without file uploads, there is nothing for the AI model to process. If you haven't read the previous article, check it out first to continue the series: Protecting routes with JWT Tokens Why Do We Need File Uploads? Consider some popular AI applications: ChatGPT allows you to upload PDFs and images. Resume analyzers require your resume. Legal AI assistants analyze contracts. Medical AI systems analyze lab reports. RAG applications build knowledge bases from documents. The workflow usually looks like this: User │ ▼ Upload File │ ▼ FastAPI │ ▼ Save / Read File │ ▼ Process using AI FastAPI makes uploading files extremely simple. Installing Required Package FastAPI uses python-multipart to process uploaded files. Install it using: pip install python-multipart Your First File Upload API FastAPI provides two important classes: File UploadFile Let's import them. from fastapi import FastAPI , File , UploadFile app = FastAPI () Creating the Upload Endpoint @app.post ( " /upload " ) def upload_file ( file : UploadFile ): return { " filename " : file . filename } Run the application. Open Swagger UI. Click POST /upload . You'll notice FastAPI automatically provides a file picker. Upload a file. Response: { "filename" : "resume.pdf" } Our API successfully received the uploaded file. Understanding UploadFile You might wonder: Why didn't we simply use a string or bytes? FastAPI provides the UploadFile class because it contains useful information about the uploaded file. Some commonly used attributes are: file . filename Returns: resume.pdf file . content_type Returns: a
AI 资讯
How AI could make it harder for governments to use hacking tools
AI is proving effective at finding and exploiting vulnerabilities. Some say this will make it harder for governments to use hacking tools and spyware and could reignite calls to backdoor devices.
创业投融资
Meeting notetaker Circleback adds a free tier to attract more customers
Circleback is also introducing new pricing plans starting from $14 per month
AI 资讯
Sessions vs JWTs: you are choosing how often you pay for state
Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for...
AI 资讯
Why your transactional email needs a queue, not a try/catch
Almost every codebase I've inherited sends email the same way: somewhere inside a POST handler, between the database write and the response, there's an await on the mail provider's SDK. It works, for months. Then one afternoon your signup endpoint starts timing out, and it takes an hour to work out that the cause is your email provider having a bad day three thousand kilometres away. I build Pulsenote , a transactional email API, so I've spent an unreasonable amount of time in the space between "your API call returned 200" and "the message is in the inbox". This post is what lives in that gap, why a try/catch doesn't cover it, and where the line sits between "you need a pipeline" and "you're overengineering a side project". The naive version Here's the code. You've written this. // users.controller.ts @ Post ( ' signup ' ) async signup (@ Body () dto : SignupDto ) { const user = await this . users . create ( dto ); await this . mailer . send ({ to : user . email , subject : ' Confirm your email ' , html : renderConfirmation ( user ), }); return { id : user . id }; } Nine lines, obvious intent, no infrastructure. For a lot of applications this is genuinely the right answer, and I'll come back to that at the end. But let's be precise about what it costs, because "it's fine" and "I haven't measured it" are different statements. It puts a third party in your request path. Your p99 for POST /signup is now your p99 plus the provider's p99. Not their median — their tail. A slow provider becomes a slow endpoint, then no endpoint. This is the failure mode that actually takes services down. If the provider degrades to five seconds per call, every signup request holds a connection and an event-loop continuation for five seconds. Your connection pool fills, your load balancer queues, health checks fail, the pod gets restarted, and now you're down — because of email. The blast radius of a non-critical dependency became the whole endpoint. A provider 5xx loses the mail entirely.
AI 资讯
Hello, DEV! I'm a Game Backend Engineer
I'm a backend engineer mainly working on game servers, with Java as my primary language. Over the years, I've spent a lot of time building and debugging backend systems, and recently I've been digging deeper into concurrency, I/O, logging, and performance. Working on game servers has taught me that many problems look simple at first, but become surprisingly complicated once the system gets busy. I'll be sharing some of the things I've learned from real-world systems, including experiments, benchmarks, design decisions, and a few open-source projects I'm working on. Glad to be here. Looking forward to learning from everyone on DEV!
AI 资讯
Stop Letting Flaky APIs Crash Your AI Agents
How to combine exponential backoff, circuit breakers, and graceful fallbacks for production-grade agentic workflows. The Bottleneck in Production AI agents are only as reliable as the tools they invoke. When an LLM decides to search the web, scrape a URL, or fetch database records, it depends entirely on network stability. In production, external APIs fail constantly. A sudden surge causes 429 rate limits, a third-party microservice throws a 504 timeout, or a target endpoint goes down entirely. The naive approach—executing raw tool calls directly inside the agent loop—is a ticking time bomb: # The Naive Anti-Pattern: Fragile Tool Execution def execute_agent_tool ( tool_name : str , payload : dict ): # One 500 error here kills the entire multi-step reasoning chain response = requests . post ( f " https://api.service.internal/ { tool_name } " , json = payload ) return response . json () When this call breaks, the unhandled exception crashes the runtime. You lose the entire reasoning graph, waste LLM tokens, and degrade the user experience. The System Architecture: Layered Tool Defense To keep multi-step agents alive, you need a defensive execution pipeline wrapped around every tool. Instead of allowing errors to bubble up and kill the agent, we handle failures across three distinct layers: Exponential Backoff : Mitigate transient network glitches and minor rate spikes by retrying with increasing delays. Circuit Breaker : Detect persistent downtime. If an API fails three times consecutively, trip the breaker to stop sending doomed requests. Graceful Fallbacks & Partial Degradation : When a primary service is down, route the query to a replica, cached store, or lightweight fallback (e.g., cached search index instead of a live browser scrape). [ Agent Core ] │ ▼ ┌───────────────────────────────┐ │ Circuit Breaker Check │ │ (Is Primary Service Up?) │ └──────────────┬────────────────┘ OPEN │ CLOSED (Healthy) ┌───────┴────────┐ ▼ ▼ ┌─────────────┐ ┌─────────────────────────
AI 资讯
Stop Guessing Your App's Resource Requirements
After development comes deployment - whether on-premise or on a cloud based environment. And then we face a simple question: how much resource should I assign to this system? What is the ideal numbers? If we get this wrong, we often need to go back time and again to fine tune - either to ensure our application is capable of handling the targeted load, or to avoid paying for resources we are not using. This article explains the approach step by step. So that we spend just enough time upfront to avoid spending exponentially more time and money at later stages. Who Is This For? This article is written primarily for developers. But if you are a manager or a CTO, there are sections written specifically for you. Feel free to jump straight there. 👉 If you are a Manager or Project Manager 👉 If you are a CTO or Architect For everyone else - the full article is worth reading top to bottom at least once. But if you are revisiting a specific topic, jump to whatever is relevant. Table of Contents Local is the Starting Point When Should You Start Thinking About Right Sizing? How Long Will This Actually Take? Start With What You Have - Your Local Setup Setting Up Your Load Generation - The Hammer The Cost of Testing - This Is Not Free Sizing Your Pod More Resources Per Pod or More Pods? Scaling - Easy to Set Up, Hard to Get Right Periodic Right-Sizing - You Are Not Done Yet Local is the Starting Point Local system is always where we start. To try things out, to check if things work. But 99% of what we test locally is the sunny day scenario. Does the MVP work? Does the happy path hold? Even if you're diligent enough to test negative scenarios, you're almost certainly not testing production-level load on your laptop. Which means you have no idea what resources your app actually needs when it matters . This is where the problem starts. On local, we routinely kill the heavy IDE, close browser tabs, shut down background processes -without ever stopping to ask: how much memory and CPU d
AI 资讯
Distributed Background Processing: Scaling Temporal Workflows with Laravel
Laravel's queue system is excellent. Redis-backed queues and supervisors can handle millions of standard jobs efficiently. However, when background processing evolves into complex, multi-day, retry-sensitive state machines, standard queues begin to show limits. Consider a multi-step user onboarding flow: Send a welcome email. Wait 3 days. Check if the user uploaded a profile picture. If not, send a reminder. Wait another 4 days. If still incomplete, flag the account for manual sales outreach. Implementing this with standard Laravel jobs requires writing complex database state tracking, configuring multiple delayed dispatch loops, and managing manual retry intervals. If a server reboots mid-process, tracking which step a user was on becomes an operational nightmare. Temporal solves this. It is a workflow orchestration engine that guarantees state progression. It allows you to write standard PHP code while Temporal handles state persistence, timeouts, queryable statuses, and complex retries. Here is how to integrate Temporal into your Laravel application. Core Architecture: Workflows vs. Activities Temporal separates execution logic into two distinct concepts to ensure reliability and fault tolerance: Workflows : The orchestrator. Workflows must be deterministic . They dictate the flow of execution, handle sleep intervals, and coordinate steps. Because they are deterministic, they must not interact directly with external systems, databases, or random functions. Activities : The execution layer. Activities can be non-deterministic. They perform the actual work: making database queries, querying third-party APIs, sending emails, or writing files. Setting Up Temporal in Laravel To communicate with a Temporal cluster, install the official Temporal PHP SDK via Composer: composer require temporal/sdk Next, configure your Temporal environment. In your .env , define the location of your Temporal address (by default, a local installation runs on port 7233 ): TEMPORAL_ADDRESS=1
AI 资讯
OAuth Failure Recovery: Why I Choose Safe Retries for Authorization and Callback Steps
Short answer: Retry the transport operation, never the OAuth meaning: keep one durable authorization attempt, accept its callback once, and make every downstream effect replayable from recorded state. For a B2B SaaS account-deletion flow, I would block new sessions before attempting remote cleanup, because deleting data while a surviving session can still act is the more dangerous ordering. That is the architecture decision. It treats a timeout as missing knowledge, not proof of failure. A callback may have committed even when the browser received no response; a token exchange may have reached the other side even when the connection closed; an account deletion may be retried by a worker after its first lease expires. The recovery design must therefore answer a narrow question at each boundary: do we know the operation did not happen, do we know it happened, or is the result still unknown? What must remain true during OAuth failure recovery? The first invariant is that an authorization attempt has one identity independent of any HTTP request. Store an opaque flow identifier, the expected callback state, the account or tenant context, a creation time, an expiry, and a small state machine such as pending , exchanging , succeeded , or failed . Don't let a browser refresh create a second logical attempt merely because it creates a second request. The second invariant is single consumption. An authorization code and its state belong to one attempt; the callback handler must atomically claim that attempt before triggering side effects. A duplicate callback should read the previously recorded outcome and return the same application-level destination. It must not provision the user again, issue another internal session, or append a second audit event that claims a second login. Exactly once is the goal, but HTTP cannot promise it by itself, so I use an exactly-once mindset at the business boundary: an atomic database transition establishes who owns the work, unique constrain
AI 资讯
AI Has Human Doctors Asking: What’s Left for Us?
A recent paper argues that AI is often better at doctoring than doctors. Guess who isn't thrilled.
AI 资讯
PostgreSQL Multi-Tenancy: Isolation That Survives a Growing Team
Startups building B2B products reach for multi-tenancy in PostgreSQL the same way on day one: one shared database, one set of tables, and a tenant_id column marking who owns each row. That is the correct call, and it stays correct for a long time. However, when that column is enforced by application code rather than by the database, a single forgotten predicate stops being a bug and becomes a disclosure event, and a disclosure event is one of the very few engineering failures that lands straight on your balance sheet as stalled enterprise deals, an unplanned legal bill, and a security review you can no longer pass. By understanding what multi-tenancy actually guarantees, which isolation model fits your stage, and how Row-Level Security moves that guarantee out of your codebase, startup CTOs and Fractional CTOs can make the tenant boundary hold without slowing the team down. (If you want to skip the theory, jump straight to the connection pooler trap that switches Row-Level Security off in production, what it costs in query performance, or when it is genuinely time to leave the shared schema.) Because "enforced by application code" means something very specific in practice. It means a promise that everyone will remember to filter on tenant_id , and that promise is the single most expensive line of undocumented policy in your entire codebase, because it holds perfectly for about fourteen months, right up until the afternoon a tired engineer ships a reporting endpoint that joins four tables and forgets the predicate on exactly one of them, and then a customer opens a dashboard and sees somebody else's invoices. That is not a bug. A bug is something you fix on Monday. A cross-tenant data leak is a disclosure event, which means legal gets involved, your enterprise prospects get an email from their own security team, and the deal that was supposed to close your Series A quietly moves to next quarter and then to never. The uncomfortable part is that this is not a story abo
AI 资讯
Webhooks vs Polling: Why Real-Time Integrations Matter in 2026
Webhooks vs Polling: Why Real-Time Integrations Matter in 2026 In modern software, knowing that something happened is often just as important as knowing what happened. A customer completes a payment. An order changes from pending to shipped. A user creates an account. A GitHub pull request is opened. A subscription is renewed. An AI workflow needs to start processing a new request. The question is simple: How does your application know that something changed? For years, developers have relied on two common approaches: polling and webhooks. Both solve the same fundamental problem—keeping systems synchronized—but they do it in completely different ways. Polling repeatedly asks an API whether something has changed. Webhooks allow the external system to notify your application when something actually happens. That difference can have a major impact on performance, scalability, API usage, responsiveness, reliability, and overall system architecture. And as applications become increasingly connected in 2026, understanding when to use each approach is more important than ever. What Is Polling? Polling is the traditional approach to checking for changes. Your application periodically sends a request to another system: “Has anything changed?” For example, imagine an e-commerce application that needs to know when an order has been paid. It might call an API every 30 seconds: GET /orders/12345 The response might say: status: pending Thirty seconds later, the application asks again. Then again. And again. Eventually: status: paid The application finally discovers that the payment has been completed. The basic workflow looks like this: Application → API → “Anything new?” API → Application → “No.” Thirty seconds later: Application → API → “Anything new?” API → Application → “No.” Eventually: Application → API → “Anything new?” API → Application → “Yes, the order has been paid.” The approach is straightforward and easy to understand. But there is a problem. Most of those requests
AI 资讯
From SOLID to Composition, Dependency Injection, and IoC: How Angular, Spring, and Node.js Differ
When learning Angular, Spring, and Node.js, I often came across terms like SOLID, Dependency Injection (DI), Inversion of Control (IoC), IoC Container, and Composition . At first, these concepts can feel like they are all the same thing. They are not. The key realization is: SOLID is about how we design software. Composition is about how we build larger systems from smaller pieces. Dependency Injection is a technique for providing those pieces. IoC containers automate that process. Understanding this relationship makes Angular, Spring, and Node.js architectures much easier to reason about. 1. SOLID Is a Design Principle, Not a Framework Feature SOLID is a collection of software design principles. For example, Single Responsibility Principle (SRP) says that a component should have a focused responsibility. Instead of having one class responsible for HTTP handling, database access, validation, email, and payment processing, we can separate those responsibilities: Controller ↓ Service ↓ Repository ↓ Database Each part has a focused job. Similarly, the Open/Closed Principle (OCP) encourages us to design components that can be extended without constantly modifying their existing implementation. These principles don't require Angular, Spring, or an IoC container. You can follow SOLID in plain JavaScript. 2. Composition Is the Bigger Idea Composition means: Build a larger behavior by combining smaller, focused pieces. This works in both functional and object-oriented programming. In functional programming: function A ↓ function B ↓ function C A larger function can be created by composing smaller functions. In object-oriented programming: OrderService │ ├── PaymentService └── EmailService OrderService is composed using other objects. The important relationship is often: HAS-A rather than IS-A For example: OrderService HAS-A PaymentService rather than: OrderService IS-A PaymentService This is one reason composition is often preferred over deep inheritance hierarchies. 3. Dep
AI 资讯
Frontend Backend Correlated Logging: Browser Fetch Request IDs and Server Logs
Short answer: give each browser fetch a request ID, carry it to the backend in a standard HTTP header, and emit that same ID in structured logs on both sides. Keep the pricing decision itself behind a flag with an explicit evaluation ID, so a rollback can be verified instead of guessed. The browser is the first audit surface Rolling out a new pricing rule in an edtech app sounds like a feature-flag task. Operationally, it is a tracing problem with money attached. A student sees a price in the browser, the frontend calls the checkout backend, and the backend evaluates a flag before writing an order. When those events cannot be joined, a rollback turns into a debate about which request produced which price. I've been paged for missed jobs and duplicate deliveries. The same failure pattern appears here: a dashboard says the system is healthy, but the individual request that matters is hard to reconstruct. A request ID doesn't prove that a price was correct. It makes the evidence joinable. The smallest useful contract is straightforward: The browser creates a non-secret request ID for each outbound fetch. The ID travels in X-Request-ID (or the equivalent header chosen by the team). The server validates or replaces malformed values, then logs the accepted value. Every log record for the request includes the ID, route, outcome, and duration. A separate flag-evaluation ID identifies the pricing decision and its rule version. Don't put a user email, token, or price in the request ID. It's a correlation key, not an authorization mechanism or a business record. How should frontend and backend logs correlate a browser fetch request ID? The browser and server need a shared boundary, not a shared logging library. For a JavaScript or Node.js application, the fetch wrapper should generate an ID before sending the request and attach it to the headers. The Node.js service should read that header at the HTTP edge, bind it to request context, and include it in every subsequent log eve
AI 资讯
NET Framework Essentials: Web Development Simplified
Your backend framework will outlive your current team. Choose one that the next team can still navigate — here's why .NET has been that framework for Netflix, GitHub, and Stack Overflow for over two decades. Summary Twenty-three years. That's how long .NET has been running in production. Most frameworks from that era got abandoned, forked beyond recognition, or replaced entirely — .NET kept showing up. Netflix still uses it. GitHub uses it. Stack Overflow, which has probably saved more developer careers than any single resource on the internet, runs on ASP.NET. None of these teams are using it out of inertia. They're using it because it works under conditions that expose every weakness in a poorly designed system. This article gets into how .NET actually works, what it gives teams day-to-day, and whether it makes sense for what you're building now. Key Takeaways: One codebase, five platforms — Windows, macOS, Linux, Android, iOS. No rewrites, no platform-specific forks. Three languages, one project — C#, F#, and Visual Basic coexist without forcing a rewrite. The performance tooling ships with it — JIT compiler, AOT compiler, CLR memory management, Garbage Collector. All out of the box. Why Is .NET Still Around? Honestly, this question is worth sitting with for a second — because in software, most things don't survive twenty years. They solve the problem of the moment, get widely adopted before anyone finds the sharp edges, and then get quietly replaced when something newer comes along and the migration pain seems worth it. .NET didn't go that way. Some of that is Microsoft backing — resources, long-term support commitments, a developer community that doesn't dissolve when priorities shift. But backing alone doesn't explain it. Plenty of well-resourced frameworks have died. What actually kept .NET alive is that the foundational architecture held up. The cross-platform capability wasn't duct-taped on in 2020 because everyone suddenly cared about Linux. It was in the
AI 资讯
Adding OpenAPI Support to Mummy, a Nim HTTP Framework
Nim doesn't have a lot of options for building HTTP APIs with the kind of batteries-included developer experience you get in frameworks like FastAPI or Express with Swagger middleware. mummy is a fast, solid HTTP/WebSocket server library for Nim (my fork with the additions below is at github.com/isaiahpeter/mummy ) — but out of the box, it doesn't generate OpenAPI specs, validate request bodies, or give you typed path parameters. So I forked it and added those. This post walks through what I built, why, and what I learned extending an existing Nim library instead of starting from scratch. Why mummy, and why OpenAPI I wanted a Nim backend for a few projects (a contact-form API, a todo API demo) and kept missing three things I'd take for granted in other ecosystems: Auto-generated API docs — a /docs endpoint you can actually hand to someone, generated from your routes instead of hand-written. Typed path parameters — pulling id out of /users/{id} as an int without manual parsing and error handling in every handler. Request validation — rejecting a bad JSON body before it reaches your handler logic, with a schema to back it up. mummy is fast and minimal by design, which is exactly why it was worth extending rather than replacing. What I added OpenAPI spec generation. I added openapi_schema.nim and openapi_router.nim , which let you wrap routes in an OpenApiRouter and attach a summary, tags, and a response schema via schemaOf . The router serves both /openapi.json and a browsable /docs page generated from your actual route definitions — so the docs can't drift out of sync with the code the way hand-written API docs do. Typed path parameters. pathParam[T](request, "id") pulls a path segment and parses it as the type you ask for, with a clean 400 response if parsing fails. One gotcha worth flagging if you try this yourself: in this Nim version, the generic dot-call form ( request.pathParam[int]("id") ) doesn't parse — you have to call it as pathParam[int](request, "id") in
AI 资讯
React Form Backends Compared: Serverless Functions vs. Form-as-a-Service
React Form Backends Compared: Serverless Functions vs. Form-as-a-Service React makes building a form straightforward. What happens after onSubmit is a different question: you still need somewhere to validate, process, store, or forward the submission. Two common approaches are writing a serverless function yourself or using a hosted form backend such as onsubmit.dev (form backend). This article compares the two, using Vercel/Netlify-style functions for the DIY approach and onsubmit.dev with its React integration as the managed example. The basic problem Imagine a typical contact form: function ContactForm () { return ( < form > < input name = "email" type = "email" required /> < textarea name = "message" required /> < button type = "submit" > Send </ button > </ form > ); } The React component is only the UI. A real application usually needs backend behavior too: accepting the HTTP request validating and sanitizing input handling errors preventing abuse or spam delivering or storing the submission keeping credentials and other secrets off the client There are two broad ways to get that backend. Option 1: Build a serverless function With platforms such as Vercel and Netlify, you can create an HTTP function alongside your application and have your React form submit to it. Conceptually, the architecture looks like this: React form | v Your serverless function | +--> validation +--> email provider +--> database +--> other services The main advantage is control. Your function owns the request lifecycle, so you decide precisely how data is validated, transformed, authenticated, stored, and forwarded. If a submission needs to update PostgreSQL, call an internal API, enqueue a job, and return application-specific data, a custom backend is usually the natural solution. Serverless functions can also reduce product-level vendor lock-in. Although platforms have their own deployment conventions, HTTP handlers and their business logic are generally portable with some work. The tr
AI 资讯
A New Way to Build Aggregation Pipelines in Go
This article was written by Lin Borland Aggregation pipelines are one of the most powerful tools in MongoDB. They let you filter, reshape, compute, and group documents in a single query. In practice, the aggregation framework feels almost like a language of its own. With its combination of stages, expressions, and operators, you can describe everything from straightforward filtering to sophisticated transformation logic. This expressive power is what makes aggregation pipelines so useful, and is also why they have a learning curve associated with them. If you’ve worked with MongoDB in Go, you may know that the existing syntax for writing pipelines in Go can be cumbersome to work with. This is especially true when a pipeline includes several stages, repeated computed logic, or deeply nested expressions. In these cases, both readability and writability may begin to suffer. There’s a need for a more Go-native way to build aggregation pipelines. This is why we’re introducing a new approach: an experimental aggregation builder in Go. In this article, we’ll compare the traditional and new approaches, then go through an example. The traditional BSON-based approach Today, if you want to build an aggregation pipeline with the Go driver, you typically do it with bson.D, bson.A, and mongo.Pipeline. While this approach is flexible, it can be hard to spot small mistakes. Let’s use a simple example from the sample_mflix.movies collection. Suppose we want to find movies released after the year 2000. Here’s a pipeline that demonstrates how easy it can be to get the shape wrong: mongo . Pipeline { bson . D {{ Key : "$match" , Value : bson . E { Key : "$gte" , Value : bson . E { Key : "$year" , Value : 2000 }}}}} At a glance, the mistake might not be obvious. The document is valid BSON, but the pipeline uses “bson.E” instead of “bson.D” for some values, resulting in a pipeline that returns zero results. If we try to fix the nesting, we can still end up with a pipeline that is structu