AI 资讯
Query Objects in PHP: Rich Filtering Without Leaking SQL Into the Domain
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You ship a list endpoint. GET /orders . It returns the customer's orders, newest first. Clean. Then product wants a status filter. Then a date range. Then "only orders over 100 euros". Then pagination. Then sort by total, descending. Six months later the controller signature reads like a tax form, and the repository method that backs it is findByCustomerAndStatusAndMinTotalBetweenDatesOrderedBy(...) with nine parameters, three of them nullable. So you "fix" it. You let the caller pass a Doctrine QueryBuilder into the repository. Or worse: the use case starts assembling WHERE clauses as strings because that was the fastest way to add the next filter on a Friday. Now your application layer knows about table aliases and SQL operators. The domain speaks SQL, and the whole point of having a repository interface is gone. There is a shape that holds: a query object the domain builds, and an adapter that translates it into SQL. The caller describes what it wants in domain terms. The adapter decides how to fetch it. The leak, concretely Here is the version that grows out of control. Every new filter is another nullable parameter. public function search ( ?string $customerId , ?string $status , ?DateTimeImmutable $from , ?DateTimeImmutable $to , ?int $minTotalCents , string $sortBy = 'placedAt' , string $direction = 'DESC' , int $page = 1 , int $perPage = 20 , ): array ; Nine parameters, half of them nullable, two of them ( sortBy , direction ) carrying raw column names that map straight to SQL. Add a filter and the signature grows again. Every caller has to remember positional order. And $sortBy is a column name leaking through the port: t
AI 资讯
A Domain Logger Port: Decoupling From PSR-3 Without Losing Context
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You open a use case that places an order. Near the top of the constructor, alongside the repositories and the payment gateway, sits a Psr\Log\LoggerInterface . The method body calls $this->logger->info(...) three times. It looks harmless. It is the most common way framework concerns leak back into a domain you spent weeks keeping clean. PSR-3 is a fine standard. Monolog is the default implementation in most PHP projects, and it earns that spot. The problem is not the library. The problem is where you point it. When LoggerInterface is a constructor argument in your application layer, your use case now depends on a package whose surface area you do not control, whose log levels you may not want, and whose context conventions are someone else's. The dependency arrow points the wrong way. What PSR-3 drags in Psr\Log\LoggerInterface is eight level methods plus a generic log() . The level taxonomy comes from RFC 5424 syslog: emergency , alert , critical , error , warning , notice , info , debug . That is a system-administration vocabulary. Your domain does not speak it. When a use case calls $this->logger->warning('payment retry') , you have to ask: is a retry a warning or a notice ? The answer is an infrastructure judgment call wearing a domain costume. The method signature also accepts an arbitrary array $context and a string|Stringable $message with {placeholder} interpolation. None of that is something your application code should be deciding. <?php declare ( strict_types = 1 ); namespace App\Application\Order ; use Psr\Log\LoggerInterface ; final readonly class PlaceOrder { public function __construct ( private OrderRepository $ord
AI 资讯
Retries and Circuit Breakers Belong in the Adapter, Not Your Use Case
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You open a use case that places an order. It loads a customer, builds the order, charges a payment gateway, saves, publishes an event. Clean. Then a sprint ago someone added a retry loop around the charge call, because the gateway flaps under load. Now the use case has a for ($i = 0; $i < 3; $i++) , a usleep() , and a comment that says // gateway is flaky on Mondays . The business rule is buried under retry plumbing. A reader who wants to know what placing an order means has to skip past sleep timers and exception counters to find it. Worse, the next person who adds a second outbound call copies the loop. Soon every call to the network has its own hand-rolled retry, each with a slightly different backoff, none of them tested. The use case learned about transient failure. It should never have. Transient failure is not a business rule A use case answers one question: what does the application do when this thing happens? Place an order. Cancel a subscription. Issue a refund. Those are decisions the business cares about. "The payment gateway returned a 503 and we should try again in 200ms" is not a business decision. It is a property of the network between your process and theirs. The domain does not know the gateway is HTTP. It does not know there is a network at all. It asked a port to charge a customer, and the port either succeeds or raises a domain exception. Here is the port, stated in domain language: <?php declare ( strict_types = 1 ); namespace App\Application\Port ; use App\Domain\Customer\CustomerId ; use App\Domain\Shared\Money ; interface PaymentGateway { public function charge ( CustomerId $customerId , Money $amount , s
AI 资讯
Persisting One Aggregate Across Multiple Tables, ORM-Agnostic
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You have an Order . It owns line items and a shipping address. In the database that is three tables: orders , order_items , order_addresses . The aggregate is one thing in the domain and three rows-with-children in storage. Now look at what most codebases do with that. The service loads the order, loops the items, saves each one in its own statement. Halfway through, a unique-constraint violation throws. The order header is already committed. Two items are in. One address is missing. You have a row in orders that no part of the system considers valid, and no single place to point at when you ask how it got there. The aggregate is supposed to be a consistency boundary. Saving its parts in separate, independently-committing operations breaks that boundary on the way to disk. This post is about closing it: one repository, one transactional save across every table, and a read path that rebuilds the whole object from rows without leaking the ORM into the domain. The aggregate the domain sees The domain class has no idea it lives in three tables. It holds its children directly and guards their invariants. <?php declare ( strict_types = 1 ); namespace App\Domain\Order ; use App\Domain\Shared\Money ; final class Order { /** @var list<LineItem> */ private array $items ; private function __construct ( private readonly OrderId $id , private readonly CustomerId $customerId , array $items , private Address $shipTo , private OrderStatus $status , ) { if ( $items === []) { throw new InvalidOrder ( 'order needs an item' ); } $this -> items = array_values ( $items ); } public static function place ( OrderId $id , CustomerId $customerId , array $it
AI 资讯
Modeling Money in PHP: Your Own Value Object vs brick/money Behind a Port
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub Open a PHP REPL and type 0.1 + 0.2 . You get 0.30000000000000004 . Now imagine that drift sitting in a total column on an invoice. The bug report does not say "IEEE 754 rounding." It says "the customer was charged one cent too much and finance wants to know why." Money is not a number. It is an amount paired with a currency, and it follows rules a float does not know about. You cannot add 10 USD to 10 EUR. You cannot store 19.99 in a binary fraction and expect it back. You cannot round a third of a cent without deciding how to round it, and that decision belongs to your business, not to your storage engine. This post builds a small Money value object that gets the currency safety right, then shows when to stop hand-rolling and wrap brick/money behind a domain port instead. The point of the port is the same point as every port: the rounding rules live in your domain, and the library stays swappable. Why a float is the wrong type Three separate problems hide inside float $amount . The first is representation. A float is a binary fraction. Decimal amounts like 0.10 have no exact binary form, so they round on every store and load. Sum a few thousand line items and the error compounds into real cents. The second is the missing currency. A bare number cannot tell you whether 1000 means ten dollars or one thousand yen. Two amounts in different currencies are not comparable, but the type system lets you add them anyway. The third is the rounding rule. When you split 10.00 across three line items, you get 3.333... per item. Someone has to decide who absorbs the leftover cent. A float makes that decision silently and inconsistently. The fix
AI 资讯
Hand-Rolled Mappers vs AutoMapper: Keeping the PHP Domain Pure at the Boundary
Book: Decoupled PHP — Clean and Hexagonal Architecture for Applications That Outlive the Framework Also by me: Thinking in Go (2-book series) — Complete Guide to Go Programming + Hexagonal Architecture in Go My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub You add a column to a table. discount_cents , nullable, default zero. A small migration, a five-minute ticket. The next morning a teammate pings you: orders placed overnight have a discount of null where the domain expected zero, and the total calculation now throws a TypeError deep inside a value object. You trace it back. A reflection-based mapper copied the new column straight onto a property the domain class did not declare, then the next read pulled it back as null and handed it to Money . Nobody wrote the line that connected discount_cents to the domain. The mapper guessed, and the guess leaked the table's shape into a class that was supposed to know nothing about tables. This is the case against automatic mapping at the persistence boundary. A hand-rolled mapper is more typing. It is also the seam that stops the database schema from reaching into your domain. What the boundary is for In a hexagonal layout the domain Order knows about line items, currencies, and the rule that an order needs at least one item. It does not know there is a orders table with a discount_cents column. The repository adapter is the only place those two worlds touch, and the mapper is the translator that stands at that seam. A reflection or annotation-driven mapper collapses the seam. It reads the persistence shape, walks the domain object's properties, and copies whatever names line up. When the two shapes match, it feels free. When they drift, the drift travels silently across the boundary you built it to protect: a renamed column, a new field, a type that does not round-trip. The whole point of the adapter is that the domain and the table can cha
AI 资讯
Beyond the Happy Path: Lessons in Resilience and Distributed State
Reflecting on two major technical challenges from my backend engineering internship, focusing on fault tolerance, infrastructure, and distributed architectures. Introduction As I wrap up my HNG internship, I’ve been reflecting on the gap between code that "works on my machine" and code that survives in production. Here is a look at two tasks from Stage 9—one solo, one team-based—that completely changed how I approach backend engineering and infrastructure. The Individual Task: Background Job Scheduler What it was For my individual Stage 9 task, I built a distributed background job scheduler backed by PostgreSQL and a FastAPI backend, featuring a vanilla HTML/CSS/JS frontend. It manages async tasks (like a mock email sending queue) using a MinHeap priority queue, Directed Acyclic Graph (DAG) dependency resolution, a Dead-Letter Queue (DLQ), and a real-time Server-Sent Events (SSE) dashboard. The problem it was solving Heavy asynchronous tasks—like email generation or batch processing—cannot block the main API thread. The system needed to successfully queue, prioritize, retry on failure, and track every job entirely independently from the standard request-response cycle. How I approached it I built the core logic from the ground up: a MinHeap and an alternative Timing Wheel algorithm for scheduling, a worker engine featuring a 3-attempt backoff sequence (1s, 5s, 25s with jitter), a DAG dependency checker, and a starvation daemon to prevent tasks from hanging. Once the CRUD API and SSE streaming were hooked up, I containerized the entire application with Docker and wrote my deploy scripts. I thought I was done. What actually broke and how I fixed it The application code took hours. The deployment took a full day of non-stop debugging across multiple cloud providers. Oracle Cloud was out of capacity on every free tier shape, and GCP demanded upfront payment. I finally got a t3.micro running on AWS, but that’s when the real DevOps nightmare began: The SSL Chicken-and-Egg
AI 资讯
Why my first RAG layer starts in Postgres, not in a standalone vector database
When people say they are "adding RAG" to a workflow, the conversation often jumps too quickly to infrastructure choices. Should this use a vector database? Should there be a reranker? Should everything go into a knowledge graph? Those are valid questions, but they are usually not the first question. The first question is narrower: What approved knowledge should the workflow be allowed to retrieve before an AI decision happens? That is why my first retrieval layer for operational AI workflows starts in Postgres, not in a standalone vector database. The Workflow Problem In operations-heavy systems, the model usually should not answer from raw memory or from a giant prompt dump. The useful context already exists somewhere else: approved response rules; handoff criteria; product or service notes; source or campaign guidance; operational decisions that were already made by humans. The hard part is not generating fluent text. The hard part is retrieving the right approved context, showing which source influenced the decision and refusing when no safe source exists. Why Postgres First For this kind of workflow, most of the surrounding data is already relational: leads or conversations; workflow names; stages and owners; human review outcomes; source metadata; trace logs; document versions. So the first technical choice is not "where do vectors live in the abstract?" It is: Where can I keep retrieval close to the operational data model? Where can I log the retrieval path and the final decision together? Where can I evolve the schema without creating a second system too early? Postgres plus pgvector is a good first answer to that set of questions. It lets me keep: documents and chunks; metadata such as allowed use and approval requirements; retrieval traces; cost estimates; human review outcomes in one place. What The First Version Needs The first version does not need to be broad. It needs to be inspectable. My narrow retrieval scope looks like this: approved response rules
AI 资讯
AI Agent Architecture: Why Process-Level Resilience Beats Proxy Gateways
The Great AI Architecture Debate When building reliable AI agents, there are two dominant approaches. Approach A: Proxy Gateway (LiteLLM, Braintrust, etc.) App sends request to Gateway Proxy which forwards to LLM Provider. Requires Docker, database, operations team. Approach B: Embedded SDK (NeuralBridge) App plus SDK sends directly to LLM Provider. One dependency, pip install. The Hidden Cost of Gateways Every proxy gateway adds 30-200ms of network latency per call. For an agent that makes 10 LLM calls, that is 300-2000ms of unnecessary overhead. Latency breakdown: Gateway overhead: +30-200ms per call Docker infrastructure: +1-3 GB RAM Database operations: +PostgreSQL maintenance Ops overhead: +0.5 FTE Why Embedding Wins Embedded reliability eliminates the network hop: Factor Gateway Embedded SDK Added latency 30-200ms ~0ms Dependencies Docker, DB, Redis 1 (httpx) Install size 500MB+ 375 KB Single point of failure Yes (proxy) No Ops cost High Zero The Hybrid Reality Gateways serve a purpose for centralized logging, auth, and rate limiting. But for latency-sensitive AI agents, embedding reliability directly in the process is strictly better. The ideal stack: embedded SDK for reliability plus lightweight observability layer on top. https://github.com/hhhfs9s7y9-code/neuralbridge-sdk NeuralBridge: Apache 2.0, 1 dependency, 375 KB.
AI 资讯
OS Architecture, Kernel, Shell & File System
🐧 Linux for DevOps — Session 2: Understanding the Kernel, Shell, OS Architecture & File System 📓 Learning in public — These are my personal notes from my Linux for DevOps & Cloud journey. I'm sharing them in a way that's easy to revisit later and hopefully useful for anyone else starting out. In the previous session, I got comfortable with Linux basics and terminal access. This session focused on understanding what actually happens behind the scenes when we run commands , how Linux is structured internally, and how files are organized on the system. These concepts might sound theoretical at first, but they're the foundation of everything you'll do in DevOps—from managing EC2 instances and Docker containers to troubleshooting production servers. The Linux Kernel: The Heart of the Operating System The kernel is the most important component of Linux. Think of it as a translator sitting between software and hardware. Applications can't directly talk to the CPU, RAM, disks, or network interfaces. Instead, every request goes through the kernel. When you run a command, open a browser, start a Docker container, or deploy an application, the kernel is responsible for making it happen. Its main responsibilities include: Responsibility Purpose Resource Management Decides which process gets CPU time Memory Management Allocates and releases RAM Process Management Creates, schedules, and terminates processes Device Management Communicates with hardware through drivers Without the kernel, Linux would simply be a collection of files with no way to interact with hardware. Types of Kernels Not every operating system uses the same kernel design. Monolithic Kernel (Linux) keeps most operating system services inside a single kernel space. This approach is extremely fast because components communicate directly. Microkernel keeps only essential functionality in kernel space and moves other services outside. This improves isolation and stability but introduces additional overhead. Hybrid K
开发者
Building Dhrishti - Part 3: Testing on a Production Grade System
I was now done with the basic setup. However, during my time working at my startup, I have learnt to think about a project wearing multiple caps. One such aspect was - With Dhrishti running on a server that was already loaded, I did NOT want the tracking application itself to be heavy. I had to set some benchmarks to ensure that Dhrishti did not consume a tonne of space while tracking the metrics. I also had a problem with unresolved requests - in my mock_services, I had a client that was continuously hitting the API Gateway service. I had to fine-tune all the requests so that I could run tests under different loads, but the advantage was that my project was easily able to discern where the client request was coming from. However, in a production scenario, you can never know where a request is coming from - obviously, we cannot resolve different customer IPs to their respective customer names. This was the first problem. I had to specify what a customer was, and what an unknown request was. I came up with the following solution - Any unresolved IPs are going to be added to a table in the UI called unresolved IP table. This would help me with debugging later. Now, any unresolved IPs which also made requests to an ENTRY-POINT into my application could be added as the customers. For this, I very simply had to filter out the unknown IPs, and keep a configurable entry-point in dhrishti.json in which I would add a bunch of entry-points (in the case of my mock micro-service architecture, only 1) Now, I could differentiate between 2 types of unknown IPs - one which was potentially a customer, one which was a background network call, not important to the working system. The next problem was with the client service itself. It was difficult to simulate, say - a million users in my system. I had essentially built a service which was only being used by 1 customer, but how would Dhrishti behave if I added multiple client IPs? Using K6 k6 is a Grafana based application that helps
AI 资讯
WebMCP Standard Proposal for Agentic Web Actuation Now Available in Chrome (Origin Trials)
Google recently announced that WebMCP is entering origin trials in Chrome 149. The new WebMCP standard proposal lets sites expose tools (e.g., JavaScript functions and HTML forms) to in-browser AI agents, which can thus reliably simulate user actions instead of resorting to possibly expensive (e.g., on-screen reading) and often unreliable guesswork (e.g., DOM scraping). By Bruno Couriol
AI 资讯
What We Learned Scanning Netflix Atlas
Clear Code Intelligence scanned a public Netflix repository: Netflix/atlas . This is not a dunk on Netflix. It is a public-code methodology test. After scanning Google zx and Microsoft agent-framework , we wanted a different kind of repository. Netflix Atlas is an observability and telemetry project with a mature platform-engineering shape. It is mostly Scala, and it includes query/evaluator logic, API modules, language-server tooling, resource files, tests, and platform integration code. That makes it a useful scan target because it tests whether a technical debt report can understand domain context. What We Scanned The Clear Code scan reviewed the public Netflix/atlas repository and produced a technical diligence PDF report. The scan measured: 1,247 repository files 706 analyzed files 89,113 lines of code 186 report findings high AI token debt risk The scorecard was mixed: Area Score Overall diligence 35/100 Projected after remediation 53/100 Delivery 96/100 Open source readiness 83/100 Architecture 45/100 Maintainability 0/100 AI governance 0/100 The delivery and open-source signals were strong. That matters because a serious report should not only criticize. It should show where the repository is already strong. The Important Lesson Is Classification Atlas is an observability/query system. That means some findings require domain-aware interpretation. For example, a generic scanner can flag evaluator-style code as dynamic execution. But in a query language, expression evaluation may be expected product behavior. The real report question is not simply "is there eval-like behavior?" The better questions are: Is this expected DSL/query behavior? Is user input constrained? Is execution sandboxed or bounded? Are failure modes tested? Are ownership boundaries clear? Is this active debt or accepted design? That distinction matters. A scanner dump can find a pattern. A useful technical debt report has to explain what the pattern means. Where AI Token Debt Appears AI toke
AI 资讯
How ESLint Actually Works: The Quality Gate Behind Modern JavaScript
A few days ago, I shared an article: You Don't Need Another Agent. You Need a Linter. Then I did what I do with anything I write: shared it around — a few publications, a few channels. Two reasons: First, feedback. I'd genuinely rather get roasted and fix my blind spots than stay comfortable and wrong. Second, let's be honest: reach. Every writer enjoys seeing a few more views. Most of the responses were positive. One wasn't. A publication rejected it with the reason: LOW_QUALITY Fair enough. It means there's room for improvement. Funny enough, my caffeinated 1 AM brain disagreed. Then it did what every developer does when someone says "this isn't good enough." It took that personally. So I went back and reread the article. And after the initial ego check, I realized something serious: The article talked in detail about ESLint, why it matters more in an AI-assisted world than ever. What it did not do was answer the question that actually matters: What is ESLint, how does it work, and why has half the JavaScript ecosystem quietly built its quality process around it? So let's fix that. Now, this isn't a sequel to my last piece about untangling vibe-coded code. It stands on its own — one thing, done properly . A complete teardown of ESLint: What it is How it works internally Why companies use it as a quality gate The different classes of problems it solves How plugins work How to write your own rules Where it fails Why it still beats many AI-based review systems Fair warning. This article is going to be technical. There will be syntax trees. There will be compiler concepts. There will be enough JavaScript internals to make frontend developers slightly uncomfortable. I'll try my best to keep it readable not letting it turn into another manual - which nobody finishes. Let's start with the question most people never ask. What Is ESLint Actually Doing? Most developers describe ESLint like this: It checks code for mistakes. Technically true. Also completely useless. That's
AI 资讯
Stop Your Agent From Replying Twice: Dedup Patterns
Ever watched an email agent reply to the same message twice? The recipient gets two near-identical responses seconds apart, screenshots them, and your carefully engineered assistant suddenly looks like a script with a stutter. Worse: under real load, this isn't a freak event. It's the default outcome if you haven't designed against it. The double-reply problem has three distinct causes, and each one needs its own fix. Let's walk through them. Why duplicates happen at all First cause: webhook redelivery . Nylas — like most webhook providers — guarantees at-least-once delivery. If your endpoint doesn't return a 200 fast enough, or a transient network blip eats the response, the same message.created notification shows up again. Process both, send two replies. Second: concurrent workers . Your handler probably runs on multiple instances — Lambda invocations, ECS tasks, worker processes. Two of them can pick up the same notification at nearly the same instant and both start generating a reply. Third: shared inboxes . Two agents (or an agent and a human) watching the same mailbox can both decide a message is theirs to answer. This one isn't a duplicate event at all — it's a coordination problem, and it's the hardest to patch at the application layer. Fix one: deduplicate deliveries Track which message IDs you've processed, and check before doing anything else: app . post ( " /webhooks/nylas " , async ( req , res ) => { res . status ( 200 ). end (); const event = req . body ; if ( event . type !== " message.created " ) return ; const messageId = event . data . object . id ; // Atomic check-and-set. If the key exists, bail. const alreadyProcessed = await db . processedMessages . setIfAbsent ( messageId , { receivedAt : Date . now (), }); if ( alreadyProcessed ) return ; await handleMessage ( event . data . object ); }); The check-and-set must be atomic. In Redis that's SET messageId 1 NX EX 86400 ; in Postgres it's INSERT ... ON CONFLICT DO NOTHING with a row-count check. G
AI 资讯
One Nullable Timestamp, Four Account States: Deriving User Status in Laravel
Most of today went into a user-management overhaul in kickoff — my Laravel starter kit. Flyout CRUD panels, bulk actions, permission assignment, and the piece I want to talk about: account status . Active, suspended, unverified, deleted. The interesting part isn't the feature. It's the modelling decision underneath it. Where do those four states actually live ? The trap: a status column The obvious move is a status enum column on users . Set it to suspended when you suspend someone, active when you reinstate, unverified until they verify their email, deleted when they're soft-deleted. It works. Until it doesn't. Now you've got a status column and an email_verified_at column and a deleted_at from soft deletes — and all three encode overlapping truths. Soft-delete a user and forget to flip status ? Now your database says the account is both active and trashed. Verify an email but the status update fails mid-request? Drift. Every place that mutates a user becomes a place that has to remember to keep status in sync. That's not a feature, that's a maintenance tax. The signals already exist. email_verified_at tells you verified-or-not. deleted_at (soft deletes) tells you removed-or-not. The only genuinely new state is suspended — an admin deliberately blocking sign-in. So that's the only thing worth storing. What we actually store: one nullable timestamp Schema :: table ( 'users' , function ( Blueprint $table ) { $table -> timestamp ( 'suspended_at' ) -> nullable () -> after ( 'email_verified_at' ); }); That's the whole migration. Not a boolean is_suspended — a nullable timestamp. Null means not suspended; a value means suspended and tells you when. A boolean throws that second fact away for free; the timestamp keeps it at no extra cost. Same instinct as email_verified_at and deleted_at — Laravel models "did this happen, and when" as a nullable timestamp everywhere, so we follow the grain of the framework. The behaviour on the model stays tiny: public function isSuspended
AI 资讯
Why an encrypted config backup breaks when you move servers — and how I fixed it in laravel-config-backup
Imagine you write a letter in a secret code that only your old house key can read. Then you move. You photocopy the coded letter, carry it to the new house… and realise the new key can't decode any of it. The letter is valid, just useless. That's effectively what happens when you back up encrypted values from a Laravel database and restore them onto a different server. I hit exactly this while working on laravel-config-backup today, so here's the problem and the fix. The real cause: Crypt is bound to APP_KEY When you store sensitive settings (think API tokens or OAuth secrets) in the database, you typically encrypt them with Crypt::encryptString() . Lovely — until you remember Crypt uses your app's APP_KEY as the key. A naive backup copies that ciphertext straight across: // Naive approach — move the ciphertext as-is $value = DB :: table ( 'settings' ) -> where ( 'key' , 'some.secret' ) -> value ( 'value' ); // this value is encrypted with the OLD server's APP_KEY The new server has a different APP_KEY . Try to decrypt → DecryptException: The payload is invalid . Your backup is technically complete but practically dead. The fix: decrypt on the way out, re-encrypt on the way in The decision is easy to state, hard to stay disciplined about: never carry ciphertext across a server boundary. Instead — On create : decrypt the values with the source server's APP_KEY , store plaintext inside the archive. Protect that archive with AES-256 and a password (a human-held secret, not the APP_KEY). On restore : re-encrypt the values with the destination server's APP_KEY before writing to the DB. Back to the analogy: you decode the letter, carry the plain letter in a locked briefcase (the password-protected archive), and re-encode it with the new house's lock on arrival. The briefcase handles security in transit — not the old code that's no longer relevant. I made that intent explicit right where the behaviour lives, in ConfigBackupService : /** * Config Backup & Restore. * * Bundl
AI 资讯
One Agent Identity Per Customer: Multi-Tenant Email
Provisioning a tenant-scoped email identity for your SaaS is one POST: curl --request POST \ --url "https://api.us.nylas.com/v3/connect/custom" \ --header "Authorization: Bearer <NYLAS_API_KEY>" \ --header "Content-Type: application/json" \ --data '{ "provider": "nylas", "workspace_id": "<WORKSPACE_ID>", "settings": { "email": "scheduling@customer-a.com" } }' No OAuth dance, no refresh token — just an address on a registered domain. The response comes back already valid: { "request_id" : "5967ca40-a2d8-4ee0-a0e0-6f18ace39a90" , "data" : { "id" : "b1c2d3e4-5678-4abc-9def-0123456789ab" , "provider" : "nylas" , "grant_status" : "valid" , "email" : "scheduling@customer-a.com" , "scope" : [], "created_at" : 1742932766 } } The data.id is a grant_id that works with every existing Nylas endpoint, and the account is live immediately. That's the primitive behind a multi-tenant pattern worth knowing: one Agent Account per customer, on each customer's own verified domain, all managed from a single application. (Agent Accounts are in beta, so the surface may shift before GA.) The architecture in one paragraph Your app runs scheduling@customer-a.com , scheduling@customer-b.com , and so on — same code path, different identities. Each account has its own policy, its own send quota, and its own sender reputation. A single application can manage accounts across an unlimited number of registered domains, so tenant count is a billing question, not an architectural one. Customer A's deliverability problems stay Customer A's; nothing they do contaminates Customer B's mail. Domains: register once, mint accounts forever The provisioning docs lay out two domain strategies you can mix freely in one application: Strategy Address format Setup Trial domain alias@<your-application>.nylas.email None — instant Your own domain alias@yourdomain.com MX + TXT records at the DNS provider For the per-customer pattern, each tenant brings their domain. You register it once per organization (picking the US
AI 资讯
Road To KiwiEngine #15: Why I Care More About Systems Than Features
One of the reasons I often find myself disagreeing with modern software trends is that many conversations revolve around features. How many features does it have? How quickly can we add more? What can we put on the marketing page? What can we announce next? Features matter. But I care far more about systems. Because at the end of the day, people don't buy features. They buy outcomes. And outcomes come from systems. The Car Analogy One of the easiest ways to explain my thinking is with cars. A car is made up of thousands of individual components. An engine. A transmission. Suspension. Brakes. Fuel systems. Electrical systems. Cooling systems. Sensors. Wiring. Each component is important. But nobody walks into a dealership and says: "I'd like to purchase six pistons, a transmission housing, and a fuel injector." They buy a car. They buy transportation. They buy a complete system. The individual parts only matter because they contribute to the overall experience. The customer doesn't want to think about every moving piece. They want to get in, turn the key, and drive. Drivers and Mechanics This is where I think technology often loses its way. Users are drivers. Engineers are mechanics. A driver should be able to: Start the vehicle Fill it with fuel Check the oil Wash it Perform light maintenance That's about it. They shouldn't need to understand combustion timing, transmission gearing, or electrical diagnostics to get to work. The mechanic, however, lives in the details. They tune the system. They replace parts. They troubleshoot failures. They recommend upgrades. They understand how the pieces fit together. Technology is exactly the same in my mind. Users should be able to focus on their goals. Engineers should focus on the machinery. Features Are Parts This is where I think software conversations sometimes become backwards. A feature is a component. A login screen is a component. A dashboard is a component. A database is a component. An API is a component. AI integra
AI 资讯
OpenAI's GPT-5.5 and Codex Reach General Availability on Amazon Bedrock
OpenAI's GPT-5.5, GPT-5.4, and Codex are now generally available on Amazon Bedrock, one month after OpenAI revised its exclusive Azure arrangement. Pricing matches OpenAI's direct rates with usage counting toward AWS commitments. Codex shifts to pay-per-token billing with no seat fees. GPT-5.4 is the first OpenAI model available in AWS GovCloud. By Steef-Jan Wiggers