Governance-as-Architecture: An experience eliminating quarterly reviews by automatically detecting architecture violations on every commit with ArchUnit and OPA
"Why does this team keep breaking layered architecture rules?" Haven't we all had the experience of opening a pull request as a reviewer and sighing in frustration at least once? Two years ago, I personally implemented Governance-as-Architecture to solve this problem, and I am going to share that experience now. It is a situation where there is a well-organized architecture document somewhere in Confluence, yet the actual code flows as if it knows nothing about it—frankly, this is not the developers' fault. As long as rules exist only within the "document to be read," it is a natural consequence that they are not followed.
Governance-as-Architecture is a paradigm shift that embeds architectural rules into the code and platform rather than in documentation, enabling automatic detection the moment violations occur. It is a structure where quarterly architecture review meetings are replaced by automated checks executed with every commit. When I first encountered this concept, I also thought, "Isn't this something only large corporations do?" but after actually implementing it, I found it could be fully applied regardless of team size.
In this article, I will share the three core paradigms that constitute Governance-as-Architecture, actual implementation methods using ArchUnit, OPA, and Backstage, and the trial and error I personally experienced during the adoption process. I hope this will provide a clue for those wondering, "Where should I start?"
Key Concepts
Why Governance Gets Trapped in a "Slow Loop"
A traditional architecture governance model can be roughly illustrated as follows: Architects document rules → Developers write code → Quarterly or monthly review meetings are held → Violations are identified → Fixes are requested. The problem lies in when the violations are discovered. By then, the code is already entangled with dozens of other lines of code.
Feedback Loop: A structure in which the system takes its output back as input and adjusts it. The slower the loop, the more errors are detected only after they have accumulated.
Our team experienced this problem firsthand as well. Code where a Controller directly referenced a Repository started in a single PR and spread across seven modules within two months, eventually requiring us to spend an entire sprint on refactoring. The goal of Governance-as-Architecture is to make this loop as short as possible. Ideally, feedback arrives the moment a developer commits code or saves it in an IDE.
Three Sub-paradigms
Governance-as-Architecture is structured with three main approaches that complement each other.
① Governance as Code (GaC)
This approach involves writing the architectural rules themselves as code and automatically executing them in a CI/CD pipeline. A prime example is the travel platform Agoda, which rewrote thousands of engineering standards in the Rego language of the Open Policy Agent (OPA) and built a structure that runs automatic verification on every commit. Documents that were dormant in Confluence were transformed into "living policies."
② Fitness Functions
Introduced by Building Evolutionary Architectures (O'Reilly), it is an automated test that measures specific attributes of an architecture—such as modularity, dependencies, and latency. When I first encountered this concept, I honestly thought, "Isn't that just an integration test?" However, the key difference lies in what it verifies. While general functional tests ask, "Does this function work correctly?", a conformance function asks, "Does this system still satisfy the architectural attributes we intended?"
Fitness Function: A concept borrowed from evolutionary algorithms, this is a function that measures how "fit" an architecture is to specific criteria. A typical example is a test that checks for the existence of "circular dependencies" between services during every build.
③ Platform-embedded Governance
This approach involves designing the platform itself so that only compliance paths are provided by default through an internal developer portal (IDP) like Backstage. When developers use Golden Path templates to create new services, they naturally adhere to standards. This eliminates the need to memorize policies.
Why Now: 2025–2026 Trends
The tool ecosystem has matured enough for these three paradigms to be practically adopted even by small and medium-sized teams. Looking at recent trends, I get the impression that this direction is becoming the default rather than a choice.
| Trend | Content |
|---|---|
| AI-based Governance | Agentic AI is evolving to automate architecture violation detection and remediation suggestions |
| Decentralized Governance | Centralized Review Board → Pre-approved Blueprint + Decentralized Decision Making |
| Mainstreaming Policy-as-Code | The majority of technology decision-makers are beginning to recognize Policy-as-Code as a core competency |
| Platform Engineering Integration | "Governance Automation" specified as one of the core components of IDP |
There is one point to note. When citing OPA-related statistics, it is necessary to carefully examine the source. Figures released by Styra, which is responsible for the commercialization of OPA, may be biased in favor of their own products. In fact, in 2025, the creators of OPA moved to Apple and Styra's enterprise subscription service was discontinued. While OPA remains a good tool, I recommend exploring alternatives such as Kyverno or Cedar rather than relying excessively on a specific vendor.
Manual governance does not scale as microservices increase. This is because even if the team grows tenfold, the number of architects does not increase tenfold.
Practical Application
The three examples represent flows for extending governance in the order of Application Layer → Infrastructure Layer → Platform Layer. Depending on the scale or situation, you can select one by one or combine the three.
Example 1: Enforcing Java Layered Architecture with ArchUnit
This example is based on a Java + Gradle (or Maven) project.
This is one of the easiest ways to get started. ArchUnit allows you to write architectural rules using JUnit tests. Since the build breaks if a test fails in CI, it can be applied immediately without any additional tools.
// ArchitectureTest.java
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.Architectures.layeredArchitecture;
@AnalyzeClasses(packages = "com.example.myapp")
public class ArchitectureTest {
@ArchTest
static final ArchRule layerDependencyRule =
layeredArchitecture()
.consideringAllDependencies()
.layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..")
.layer("Repository").definedBy("..repository..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller")
.whereLayer("Repository").mayOnlyBeAccessedByLayers("Service");
@ArchTest
static final ArchRule noDirectDbAccessFromController =
noClasses()
.that().resideInAPackage("..controller..")
.should().dependOnClassesThat()
.resideInAPackage("..repository..");
}If you reference the Repository directly in the Controller, this error is displayed during the build:
Architecture Violation [Priority: MEDIUM] - Rule 'no classes that reside in a package
'..controller..' should depend on classes that reside in a package '..repository..''
was violated (1 times):
Field <com.example.myapp.controller.OrderController.orderRepository>
has type <com.example.myapp.repository.OrderRepository>
in (OrderController.java:18)It tells you exactly which file and line the rule is broken. CI resolves it without the need for code review to catch it.
| Code Element | Role |
|---|---|
@AnalyzeClasses |
Specify package to analyze |
layeredArchitecture() |
Define allowed dependency directions between layers |
@ArchTest |
Integrated with JUnit 5, automatic execution in CI |
noDirectDbAccessFromController |
Rule prohibiting direct access from Controller to Repository |
Example 2: Enforcing Kubernetes Policies with OPA/Gatekeeper
This example is based on an environment with a Kubernetes cluster and OPA Gatekeeper installed.
There are cases where the application layer alone is insufficient. If you want to extend governance to the infrastructure layer, OPA is a good choice. In the example below, we create a policy using Gatekeeper ConstraintTemplate that requires every Kubernetes Deployment to have the team label.
# required-labels.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: requiredlabels
spec:
crd:
spec:
names:
kind: RequiredLabels
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package kubernetes.admission
violation[{"msg": msg}] {
input.review.kind.kind == "Deployment"
not input.review.object.metadata.labels.team
msg := sprintf(
"Deployment '%v'에 'team' 레이블이 없습니다. 소유팀을 명시해 주세요.",
[input.review.object.metadata.name]
)
}
violation[{"msg": msg}] {
input.review.kind.kind == "Deployment"
container := input.review.object.spec.template.spec.containers[_]
not container.resources.limits
msg := sprintf(
"컨테이너 '%v'에 resources.limits가 없습니다. 리소스 상한을 명시해 주세요.",
[container.name]
)
}# required-labels-constraint.yaml (실제 정책 적용)
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RequiredLabels
metadata:
name: deployment-must-have-team-label
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]By doing this, the moment kubectl apply is executed, Deployments that violate the policy are not deployed to the cluster at all. This effectively blocks incorrect resources from being deployed in the production environment.
| Components | Roles |
|---|---|
ConstraintTemplate |
Register policy logic (Rego) as CRD |
violation |
Definitions of Policy Violation Conditions and Error Messages |
| Constraint Resource | Specify which resources to apply the policy to |
Example 3: Implementing Platform-Level Governance with Backstage Scorecards
You can experience the effects starting from the point where you are operating 10 or more services, or when team, on-call, and document management have become chaotic.
If you have expanded to the infrastructure layer, it is now time to move up to the platform layer. As the number of services increases, it becomes difficult to check things like "Is this service being monitored?" or "Is an on-call representative registered?" for each one individually. Using Backstage's Scorecards feature, you can display a score indicating how well each service complies with standards.
First, add a catalog file to the root of each service repo:
# catalog-info.yaml (각 서비스 레포 루트에 위치)
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payment-service
annotations:
backstage.io/techdocs-ref: dir:.
pagerduty.com/service-id: P1234567
github.com/project-slug: myorg/payment-service
spec:
type: service
lifecycle: production
owner: team-payments
system: payment-platform
dependsOn:
- component:order-service
- resource:postgres-paymentsThen, define the scorecard criteria in the Backstage backend plugin (plugins/tech-insights-backend/src/checks.ts):
// plugins/tech-insights-backend/src/checks.ts
import { Entity } from '@backstage/catalog-model';
export const productionReadinessChecks = [
{
id: 'has-owner',
name: '소유팀 명시',
description: 'catalog-info.yaml에 owner가 정의되어 있어야 합니다',
check: (entity: Entity): boolean => Boolean(entity.spec?.owner),
},
{
id: 'has-pagerduty',
name: 'PagerDuty 연동',
description: '온콜 알림을 위한 PagerDuty 서비스 ID가 등록되어 있어야 합니다',
check: (entity: Entity): boolean =>
Boolean(entity.metadata?.annotations?.['pagerduty.com/service-id']),
},
{
id: 'has-techdocs',
name: '문서화 완료',
description: 'TechDocs 참조가 설정되어 있어야 합니다',
check: (entity: Entity): boolean =>
Boolean(entity.metadata?.annotations?.['backstage.io/techdocs-ref']),
},
];You can view the scores of each service at a glance on the developer portal, and for services with low scores, you can receive immediate guidance on "what needs to be improved." Architects do not need to personally go around checking on them.
Pros and Cons Analysis
I have honestly summarized the pros and cons I experienced while implementing it myself.
Advantages
| Item | Content |
|---|---|
| Fast Feedback Loop | Switch from quarterly reviews to automatic verification on every commit. Violations are detected immediately |
| Consistency Guaranteed | The same standards apply regardless of team size or experience |
| Bridging the Document-Reality Gap | Since rules exist in code, there is no discrepancy between documentation and implementation |
| Reduced Cognitive Load | Developers do not need to memorize policies because the platform provides the right direction |
| Scalability | Governance quality does not collapse even as the team grows |
Disadvantages and Precautions
I remember having such high expectations in the beginning that I jumped in thinking, "Let's just automate everything," only to find that I couldn't deploy it even after a month. The precautions below stem from that experience.
| Item | Content | Response Plan |
|---|---|---|
| Initial investment cost | It takes significant time to translate all architecture rules into code | It is recommended to start with the 2–3 most frequently violated rules |
| Risk of reduced flexibility | Overly strict rules can hinder experimentation and innovation | It is recommended to explicitly design escape hatches |
| Learning Curve | Learning tools such as OPA/Rego and ArchUnit, as well as organizational culture change, is required | We recommend a method of first cultivating champions within the team and gradually expanding |
| Governance Code Maintenance | As the system evolves, governance code must also be updated | It is recommended to manage governance rules as subjects for PR reviews and testing, just like regular code |
| Changes in the tool ecosystem | Uncertainty exists regarding core tools, such as the departure of the OPA founder (as of 2025) | You can introduce an abstraction layer to avoid excessive reliance on specific tools, or consider alternatives such as Kyverno and Cedar |
OPA (Open Policy Agent): A general-purpose policy engine capable of integration into various environments such as microservices, Kubernetes, and API gateways. Policies are written using a declarative language called Rego. Rego: The policy language used by OPA, which separates data and queries to declaratively express "allowance or ambiguity."
The Most Common Mistakes in Practice
-
Attempting to automate all rules at once: I made this mistake initially as well. With the mindset of "if I'm going to do it anyway, I might as well do it all," I tried to translate dozens of rules into code at once, which resulted in an overload. It is much more sustainable to start by automating just the single rule that is violated the most.
-
Not designing exception paths: Our team also encountered a situation where we needed to handle certain rules as temporary exceptions due to legacy code. Since there was no exception mechanism, developers began finding ways to bypass the governance code itself. It is advisable to design a process that allows intended exceptions through code review.
-
Positioning governance as a monitoring tool: Introducing it with the attitude of "We are now monitoring all your code" will provoke resistance from the team. I remember having a rough patch once after realizing this too late. It is much more effective to communicate from the perspective that it is "a guardrail that helps our team deploy faster and more safely."
In Conclusion
Looking back on those moments when I sighed every time I opened a PR—the problem wasn't the developers or the architects. The problem was an environment where "nothing happens immediately even if the rules aren't followed." Governance comes alive only when enforced by the system, not just by documentation.
There are 3 steps you can start right now:
-
It is recommended to select one of the most frequently violated architecture rules and write it in ArchUnit or OPA. If it is a Java project, you can add
testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'tobuild.gradleand start with the simplest layer dependency rule. It is also a good approach to start with "Print warnings only" at this stage and switch to "Fail build" once the team becomes familiar with it. -
You can include the newly written governance test in the CI pipeline. If you use GitHub Actions, you can configure it to run alongside existing test steps. If you separate it into a separate job initially, failures will not affect the main build, allowing the team to get used to it without pressure.
-
We recommend organizing a software catalog using Backstage or a similar IDP tool and defining just one or two scorecard items first. Even if you start by simply checking whether the team owning each service is specified or whether documentation exists, the entire team will be able to grasp the status at a glance.
Reference Materials
- Governance as Code: An Innovative Approach to Software Architecture Verification | Agoda Engineering
- How Agentic AI Empowers Architecture Governance | O'Reilly Radar
- Automating Architectural Governance | Building Evolutionary Architectures, 2nd Ed.
- Empower your teams with modern architecture governance | Noise/GetOto
- Architectural Fitness Functions: Automating Modern Architecture Governance in .NET | DevelopersVoice
- How Policy-as-Code Enhances Infrastructure Governance with OPA | env0
- Introducing architecture governance to tackle microservices sprawl | vFunction
- Fitness Functions for Your Architecture | InfoQ
- What is Spotify Backstage and how does it work in 2025? | GetDX
- C4 Model Official Site