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

标签:#p

找到 16091 篇相关文章

AI 资讯

DevOps Salaries & Hiring in India 2026: What 800+ Live Job Listings Reveal

If you're a DevOps, SRE, or Cloud engineer in India — or hiring one — the market in 2026 looks very different from a few years ago. Instead of guessing, we analyzed 800+ live DevOps/SRE/Cloud/Platform Engineering roles currently on PuneOps to see what's actually being hired for right now. Here's what the data shows. 1. Bangalore dominates, but the market is genuinely national DevOps hiring in India is no longer a one-city story. Of the live roles: Bangalore — the clear leader, ~25% of all listings Pune — a strong #2 (and a serious DevOps hub, not just an IT-services town) Hyderabad, Mumbai, Delhi NCR, Chennai — all with steady, healthy demand Remote / Pan-India — roughly a third of all roles don't tie you to a city at all Takeaway for candidates: you're no longer limited to wherever you live. Remote and pan-India DevOps roles are a huge and growing slice of the market. 2. This is a senior-heavy market The single most striking pattern: DevOps hiring in India skews experienced. The largest band by far is 5–10 years of experience A meaningful chunk wants 10+ years (architects, principals, platform leads) Entry-level (0–2 years) roles are comparatively rare Takeaway: DevOps remains a hard field to break into directly. Most roles assume you've already done software, sysadmin, or cloud work. If you're junior, the path in is usually via a software/ops role, then specializing. 3. The skills employers actually ask for Across the listings, the same technologies show up again and again: Kubernetes — effectively table stakes now Terraform / IaC — infrastructure-as-code is expected, not bonus AWS / Azure / GCP — cloud fluency, often multi-cloud CI/CD pipelines, observability, and Python for automation Takeaway: if you're leveling up, Kubernetes + Terraform + one major cloud is the core combination Indian employers are screening for in 2026. 4. Salary ranges (market benchmarks, 2026) Compensation varies widely by company type (product vs. services), city, and exactly how senior t

2026-06-14 原文 →
AI 资讯

rclone crypt: encrypt files client-side before they touch any cloud

If you want files encrypted before they ever reach a cloud provider — so the provider only ever sees ciphertext — rclone crypt is the simplest tool that works with almost any backend (S3, Google Drive, Dropbox, pCloud, Backblaze B2, a plain SFTP box…). This is client-side, zero-knowledge-style encryption you fully control. Here's a clean setup. The idea rclone crypt is a wrapper remote : it sits on top of a normal remote and transparently encrypts file contents and file/dir names on the way up, decrypts on the way down. Your passphrase never leaves your machine. local files -> [crypt remote: encrypt] -> [storage remote] -> cloud (sees ciphertext only) 1. Install curl https://rclone.org/install.sh | sudo bash # or: sudo apt install rclone rclone version 2. Configure the underlying storage remote rclone config # n) New remote -> name it e.g. "drive" -> pick your provider -> OAuth/keys Test it: rclone lsd drive: 3. Add a crypt remote on top rclone config # n) New remote -> name "secret" -> storage: "crypt" # remote> drive:encrypted # a subfolder on the storage remote # filename_encryption> standard # also encrypts file names # directory_name_encryption> true # password> (generate a strong one) # password2> (salt - optional but recommended) Back up the passphrase + salt in a password manager. There is no recovery if you lose them — that's the whole point of zero-knowledge. 4. Use it # Upload (everything is encrypted client-side first): rclone copy ~/Documents secret: -P # List (decrypted view, local only): rclone ls secret: # Mount as a normal folder: rclone mount secret: ~/CloudCrypt --vfs-cache-mode writes On the provider's side you'll see only opaque names like a1b2c3d4... — no filenames, no content. 5. Verify the provider sees nothing rclone ls drive:encrypted # raw view = encrypted blobs + scrambled names If you can read filenames here, filename encryption isn't on — recheck step 3. Gotchas crypt encrypts content + names, not the number of files or their sizes. A m

2026-06-14 原文 →
AI 资讯

I Built a Web App That Finds the Fairest Meeting Spot for Any Group (and It's Free)

The Problem Nobody Talks About Picture this: You're trying to find a place to meet up with friends. Someone suggests a coffee shop. It's 8 minutes from their house. It's 45 minutes from yours. You say yes anyway, because suggesting a different place feels awkward. This happens all the time — with friends, with remote teams, with family scattered across a city. And the worst part? Most "meet in the middle" suggestions aren't actually in the middle. They're just the geographic midpoint, which completely ignores traffic, transit options, and the fact that roads don't go in straight lines. I got frustrated enough to build something about it. Meet Meetle Meetle is a free web app that finds the fairest meeting spot for any group of people — based on real travel times , not just distance. A Chrome Extension is coming soon so you'll have it one click away in your toolbar. You add everyone's starting location, choose how each person is traveling (driving, walking, or transit), hit Find Meeting Point , and Meetle does the math across every person simultaneously. It then surfaces the best nearby cafés, restaurants, parks, gyms, or whatever venue type you're looking for — ranked by actual fairness. No more "it's fine, I don't mind the drive." Now you have data. How It Actually Works Under the hood, Meetle uses three Google Maps APIs working together: Distance Matrix API calculates travel time from every person's location to every candidate venue, simultaneously. This is the core of the fairness scoring — you can't rank venues fairly without knowing everyone's actual travel time to each one. Places API finds candidate venues near the calculated center point. You can filter by type (coffee, food, parks, gyms, etc.), price level, minimum rating, and whether they're open right now. Maps JavaScript API renders everything visually — the map, the travel zones (isochrones), and the markers for each suggested venue. The scoring works two ways and you can toggle between them: Fairness mo

2026-06-14 原文 →
AI 资讯

You Are Not Underpaid Because You Are Foreign. You Just Never Saw The Number.

I place developers with US tech companies for a living. Before that sentence makes you close the tab: what follows is the thing I tell developers for free, one conversation at a time, until I got tired of saying it one person at a time. Last month a developer in Prague asked me if 55 dollars an hour was a reasonable rate. Nine years in. Kotlin, AWS. He had built and run a payment system for one of the largest Czech fintechs. Three million transactions a month. Zero P0 incidents in two years. A profile most US startups would fight over. I told him what the US market actually pays for that exact stack at that exact level. He went quiet for about thirty seconds. Then he said: "I have been contracting for three years. I just did the math." He had left roughly 180,000 dollars on the table. Not because he was not good enough. Because no one had ever told him the number. This is the most expensive blind spot in our industry, and almost nobody outside the US escapes it. So let me walk through why it happens, because once you see it you cannot unsee it. You are pricing against the only benchmark you have ever seen When you set your rate, you do not pull it from nowhere. You anchor it to something. And the only thing you have ever had to anchor to is your local market. So a senior engineer in Warsaw prices against Warsaw. One in Bucharest against Bucharest. You take the local senior salary, maybe add a premium because the client is foreign, and you land on a number that feels brave. Forty-five an hour feels brave when the engineer at the next desk makes the local equivalent of twenty. Here is the disruptive part. The US client is not paying for your location. They are not even thinking about your location, except as a logistics detail. They are paying for the work, and what that work is worth to their business. A payment system that does not go down is worth the same to a US fintech whether the person who built it sits in San Francisco or Brno. The value did not get cheaper w

2026-06-14 原文 →
AI 资讯

I Cut Our Image Captioning Costs 60% — Here's the Backend Story

Check this out: i Cut Our Image Captioning Costs 60% — Here's the Backend Story Look, I'll be honest. Six months ago I didn't think twice about image captioning. We were a small team, traffic was low, and we just threw everything at GPT-4o because it was the path of least resistance. Then our infra bill came in, my manager did that thing where he just stares at the dashboard, and suddenly I was a "cost optimization" guy. fwiw, that was not in my job description. This is the story of how I went from "we just use GPT-4o for everything" to a multi-model setup that cut our spend by more than half, with quality that — imho — is actually better than what we had before. No, this is not a sponsored post. Yes, I am going to mention Global API at the end because they made my life easier. More on that in a bit. Why Image Captioning Was Even on My Radar Our product has a lot of user-uploaded images. Think: product photos, profile pictures, the usual suspects. For each one we need a short, accessible caption that we use for SEO, alt text, and a downstream tagging pipeline. The downstream pipeline, btw, is the part that actually makes us money. Garbage captions in, garbage tags out. We were calling gpt-4o for everything. Every image. No caching. No batching. No thought. Each call cost us $2.50 per million input tokens and $10.00 per million output tokens. You don't have to be a math PhD to know that scales badly. I was not a math PhD. I am still not a math PhD. But I can do division. When I started pulling the numbers, the situation was grim. We were processing roughly 8 million images a month, and each one was generating more tokens than it needed to. I found one image in the logs that had produced a 4,000-token caption. The image was a screenshot of an error message. The caption was longer than the error. The Wake-Up Call: Actually Reading the Catalog One Saturday morning, coffee in hand, I decided to actually look at what was on offer. I'd been ignoring the multi-model world b

2026-06-14 原文 →
AI 资讯

GLM 5.2 Just Dropped: What Zhipu's New Open-Weights Flagship Means for Developers

Introduction Zhipu AI (THUDM) has officially released GLM 5.2 , the latest iteration of its flagship open-weights model family. Announced today by Jie Tang on Twitter, the release is already making waves on Hacker News — racking up 269 points and 146 comments within hours. For developers who have been watching the open-weight LLM race, this is a significant moment. What's New in GLM 5.2 GLM 5.2 builds on the GLM-4 series that put Zhipu on the global map. The release focuses on three areas that matter most to production teams: Stronger reasoning and coding : Improved performance on multi-step reasoning benchmarks and competitive code generation against closed-source models like GPT-5 and Claude 4.5. Better multilingual behavior : GLM has always been strong in Chinese; 5.2 pushes English-quality code reasoning and longer-context retrieval closer to frontier levels. Longer context window : Reports point to a 200K+ token context with reduced degradation on long-document tasks — useful for codebase-level analysis. Weights, inference code, and a technical report have landed on Hugging Face under the THUDM organization, with an OpenAI-compatible API endpoint exposed by Zhipu's own platform. Why It Matters The open-weights race has consolidated around a handful of serious contenders — Llama, Qwen, DeepSeek, Mistral, and now GLM. Zhipu's positioning is unique: a Chinese lab that consistently weights-and-releases frontier-class models while still maintaining a hosted commercial API. For developers, that translates to real options: You can self-host on a single H200 or a pair of RTX 5090s and skip per-token API costs entirely. You can route between self-hosted GLM 5.2 and a hosted Anthropic/OpenAI endpoint depending on cost, latency, and capability. You get an OpenAI-compatible endpoint, so dropping GLM into an existing stack is a config change, not a rewrite. The Bigger Picture GLM 5.2 lands on the same week that U.S. regulators have reportedly cracked down on Anthropic model

2026-06-14 原文 →
AI 资讯

PostgreSQL 22P01 Error: Causes and Solutions Complete Guide

PostgreSQL Error 22P01: Floating Point Exception PostgreSQL error code 22P01 is raised when a floating-point operation produces an exceptional result that cannot be represented as a valid number. This typically occurs during division by zero on float types, operations involving NaN (Not a Number), or arithmetic that yields Infinity . It is most commonly encountered in analytics, financial calculations, and data pipelines processing external or sensor data. Top 3 Causes 1. Division by Zero on Float Types Unlike integer division (which raises 22012 ), dividing a float by zero triggers a floating-point exception. This is especially common in ratio and rate calculations where the denominator can become zero at runtime. -- Problematic query SELECT total_sales :: float / total_orders :: float AS avg_order_value FROM daily_stats ; -- Safe fix using NULLIF SELECT date , total_sales :: float / NULLIF ( total_orders , 0 ):: float AS avg_order_value FROM daily_stats ; 2. NaN Values in Arithmetic Operations Data ingested from external systems, CSVs, or APIs may silently introduce NaN values into float columns. Once NaN participates in arithmetic, results become unpredictable and can trigger exceptions downstream. -- Detect NaN values (NaN is the only value not equal to itself) SELECT id , value FROM sensor_readings WHERE value != value ; -- Replace NaN with NULL safely UPDATE sensor_readings SET value = NULL WHERE value != value ; -- Filter NaN in aggregations SELECT device_id , AVG ( value ) FILTER ( WHERE value = value ) AS clean_avg FROM sensor_readings GROUP BY device_id ; 3. Infinity Arithmetic Conflicts Storing 'Infinity'::float or '-Infinity'::float is valid in PostgreSQL, but performing certain operations on them produces mathematically undefined results (e.g., Infinity - Infinity = NaN ), which can cascade into a floating-point exception. -- Check for Infinity values SELECT id , measurement FROM raw_data WHERE measurement IN ( 'Infinity' :: float , '-Infinity' :: float

2026-06-14 原文 →
AI 资讯

agentic workflows are being domesticated by actions

GitHub's Agentic Workflows preview has the kind of headline that makes people reach for the wrong conclusion. Natural language Markdown can turn into GitHub Actions workflows. That sounds like "the YAML is going away." I do not think that is the interesting story. The interesting story is that the agent is not escaping the workflow engine. It is being pulled into it. That matters because a lot of agent demos still pretend the future is a smart process floating above the boring machinery: the agent understands the request, edits the repo, runs some commands, and hands back a neat result. Nice demo. Very clean. Production engineering is not clean like that. Production engineering has permissions, logs, runner groups, approval rules, secrets, firewalls, budgets, weird old repositories, compliance questions, and someone who has to explain what happened when the helpful automation did something surprising. So the shape of Agentic Workflows is useful precisely because it is less magical than the demo version. GitHub is putting agents inside the same CI/CD world that already carries a lot of organizational trust. That is the right direction. markdown is not the control plane The cute part is that a developer can describe a workflow in Markdown and have GitHub turn that into standard Actions YAML. That is useful. YAML is not a personality test, and most teams have better things to do than memorize every Actions syntax edge case. But Markdown is only the input surface. The control plane is still Actions. That distinction matters. If the generated workflow is a normal Actions workflow, then all the existing machinery can still matter: repository permissions, runner selection, logs, environments, approvals, branch protection, organization policy, and whatever security controls the company already built around CI. This is where I get more optimistic about agentic tooling. The bad version of agents asks every organization to trust a new, parallel execution model because the mode

2026-06-14 原文 →
AI 资讯

Why Your Business Needs an AI Integration Strategy (Not Just an AI Tool)

The hype cycle for AI adoption in businesses often follows a familiar, and often frustrating, trajectory. It begins with the undeniable allure of a powerful new tool—seeing ChatGPT effortlessly summarize documents or Copilot intelligently autocomplete code is undeniably impressive. The immediate reaction is almost universally: "We need to get AI." So, tools are procured, workshops are run, and initial enthusiasm soars. Yet, more often than not, six months down the line, that excitement has waned, and the needle on actual business transformation hasn't moved. The fundamental operational rhythm of the organization remains unchanged. As a Senior IT Consultant and Digital Solutions Architect with over a decade of experience, I've observed this pattern repeatedly across various client engagements. This isn't a failure of the technology itself, but rather a profound strategy failure . It’s the single most common and costly mistake I encounter in AI adoption today. The Critical Distinction: Tool Acquisition vs. Strategic Integration Think of buying an AI tool like purchasing a state-of-the-art machine for a workshop. Its inherent value is immense, but if it sits in a corner, unused or without a defined process around it, that value remains entirely theoretical. It's an expense, not an asset generating return. Real AI integration , on the other hand, is a disciplined, multi-faceted endeavor. It's about deeply understanding your existing business processes, meticulously identifying precisely where intelligent automation can create genuine, measurable leverage. It involves designing a holistic system that delivers this leverage, and critically, establishing robust mechanisms to measure the actual outcomes against predefined business objectives. This holistic approach is strategy. The tools—be it an LLM, a specific AI platform, or an automation suite—are merely components that strategy carefully selects and orchestrates to achieve a greater aim. Where AI Integration Truly Crea

2026-06-14 原文 →
AI 资讯

Struct Embedding in Go: Composition That Bites When You Reach for Inheritance

Book: The Complete Guide to Go Programming 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 come to Go from a language with classes. You see struct embedding for the first time, and it reads like inheritance. A field with no name, methods that "carry over" to the outer type, a base struct that your type extends. So you write code the way you always have, and most of it works. Then a method does something you did not ask for, a type satisfies an interface you never meant to implement, or two embedded types fight over a name and the compiler shrugs until the exact line that calls it. Embedding is not inheritance. It is composition with a syntax that promotes methods and fields up one level. Once you hold that distinction, the surprises stop being surprises. Here is where they come from. Embedding promotes, it does not subclass Write an embedded field by giving a type with no field name: type Engine struct { Horsepower int } func ( e Engine ) Start () string { return "vroom" } type Car struct { Engine // embedded Brand string } Car now has a Start method and a Horsepower field, both promoted from Engine . You can write car.Start() and car.Horsepower as if they were declared on Car . car := Car { Engine : Engine { Horsepower : 300 }, Brand : "Fiat" } fmt . Println ( car . Start ()) // vroom fmt . Println ( car . Horsepower ) // 300 This is where the inheritance illusion starts. car.Start() is sugar. The compiler rewrites it to car.Engine.Start() . The receiver of Start is still an Engine , never a Car . There is no base class, no super , no virtual dispatch. Engine does not know Car exists. That last point is the one that bites. A promoted method runs against the embedded value, not the outer struct. The method that ignores the outer struct Say you want a stringer on the embe

2026-06-14 原文 →
创业投融资

Typescritp: Sobrecarga de Construtor

Introdução Assim como funções, construtores podem ter múltiplas assinaturas: O problema class Evento { constructor ( id : string , tipo : string , competencia : string ) { ... } // como aceitar também só id e tipo, sem competencia? } Solução — overload signatures class Evento { id : string ; tipo : string ; competencia : string ; // assinaturas constructor ( id : string , tipo : string ); constructor ( id : string , tipo : string , competencia : string ); // implementação constructor ( id : string , tipo : string , competencia : string = " nao-definida " ) { this . id = id ; this . tipo = tipo ; this . competencia = competencia ; } } new Evento ( " 1 " , " R-2010 " ); // ✅ primeira assinatura new Evento ( " 1 " , " R-2010 " , " 2024-01 " ); // ✅ segunda assinatura

2026-06-14 原文 →
AI 资讯

defer in Loops: The Resource Leak Go Still Lets You Write

Book: The Complete Guide to Go Programming 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 wrote a function that walks a directory and reads every file. It opened cleanly in review. It passed tests on a fixture folder with three files. Then a customer pointed it at a folder with 20,000 files, and the logs filled with: open /data/batch/img-08431.png: too many open files The code never closed anything early. It deferred every close. And that is exactly the problem. defer runs at function return, not end of iteration defer schedules a call to run when the surrounding function returns. Not when the current block ends. Not when the loop iteration ends. When the function returns. Most of the time that distinction does not matter, because most deferred calls live in short functions that return quickly. Put a defer inside a for loop, though, and every iteration adds one more deferred call to a stack that does not unwind until the whole loop is done and the function exits. Here is the version that ships: func processFiles ( paths [] string ) error { for _ , p := range paths { f , err := os . Open ( p ) if err != nil { return err } defer f . Close () // runs at RETURN, not here if err := handle ( f ); err != nil { return err } } return nil } Read it the way the language reads it. On the first iteration, os.Open returns a file handle and you defer its close. On the second iteration, another handle, another deferred close. By the time the loop has touched 20,000 files, you are holding 20,000 open descriptors, and not one of them closes until processFiles returns. Your process has a file-descriptor limit. On Linux the soft default is often 1024. You blow through it somewhere around the 1024th file, and the error you get back says nothing about defer . It says too many open files , wh

2026-06-14 原文 →
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

2026-06-14 原文 →
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

2026-06-14 原文 →
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

2026-06-14 原文 →
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

2026-06-14 原文 →