Skip to content

ADR-0007: Pilot activity bounded context - #35402

Merged
lukeheath merged 6 commits into
mainfrom
activity-bounded-ctx
Dec 5, 2025
Merged

ADR-0007: Pilot activity bounded context#35402
lukeheath merged 6 commits into
mainfrom
activity-bounded-ctx

Conversation

@getvictor

@getvictor getvictor commented Nov 9, 2025

Copy link
Copy Markdown
Member

See new ADR doc in this PR

Summary by CodeRabbit

  • Documentation
    • Added architectural reference documentation to support internal infrastructure planning and decision-making processes.

@getvictor

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Nov 9, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Nov 9, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request adds ADR-0007, documenting the pilot phase for extracting an Activity bounded context as part of Fleet's modular monolith transition, and updates the ADR index to include references to ADRs 0006 and 0007.

Changes

Cohort / File(s) Change Summary
ADR Documentation
docs/Contributing/adr/0007-pilot-activity-bounded-context.md
Adds new ADR-0007 document outlining the pilot activity bounded context extraction, including scope (audit portion of activities), rationale, design guidelines, folder structure, database naming conventions, API boundaries, ownership rules, dependencies across contexts, architecture enforcement plans, and 2-week success evaluation criteria.
ADR Index
docs/Contributing/adr/README.md
Updates the ADR index to add two new entries: ADR-0006 (Replace "No Team" with real default team) and ADR-0007 (Pilot activity bounded context).

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

  • Documentation-only changes with no code logic, dependencies, or functional impact
  • Index updates are straightforward reference additions
  • No architectural decisions to validate beyond document structure and content clarity

Suggested reviewers

  • lukeheath

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is minimal and only directs readers to see the new ADR doc, lacking detail about the change, testing performed, and missing most required checklist items from the template. Expand the description to explain the key changes, whether testing was performed, and complete the applicable checklist items from the PR template.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding ADR-0007 documenting a pilot for extracting an Activity bounded context, which matches the added documentation file.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch activity-bounded-ctx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
docs/Contributing/adr/0007-pilot-activity-bounded-context.md (1)

106-120: Add language identifier to fenced code block.

The folder structure code block is missing a language specifier. Add diff or leave blank if structure-only, but best practice is to specify the language for syntax highlighting.

-```
+```
 server/
 └── activity/

(Note: If representing a filesystem structure, you can also use diff or text as the language identifier.)

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 480a73c and 781517b.

📒 Files selected for processing (2)
  • docs/Contributing/adr/0007-pilot-activity-bounded-context.md (1 hunks)
  • docs/Contributing/adr/README.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
docs/Contributing/adr/0007-pilot-activity-bounded-context.md

[style] ~52-~52: Consider replacing this word to strengthen your wording.
Context: ...s. This is a generic architectural term and does not refer to Go modules (the d...

(AND_THAT)


[style] ~332-~332: Consider using a different adjective in this context to strengthen your wording.
Context: ...lities extraction. Vulnerabilities is a good candidate for the next bounded context ...

(GOOD_ALTERNATIVE)

🪛 markdownlint-cli2 (0.18.1)
docs/Contributing/adr/0007-pilot-activity-bounded-context.md

106-106: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (12)
docs/Contributing/adr/README.md (1)

46-47: ADR index entries follow existing conventions.

Both new ADR entries are formatted correctly and consistent with existing list items. The entries are properly linked to their corresponding ADR files.

docs/Contributing/adr/0007-pilot-activity-bounded-context.md (11)

11-29: Context section is thorough and well-reasoned.

Excellent use of industry examples (GitLab, Kubernetes) with concrete evidence. The distinction between this pilot (audit portion only) and the complete activity system is clear, and the trade-offs are transparently acknowledged. The learnings from ADR-0001 are well-integrated.


31-46: Design guidelines are clear and actionable.

The bounded context design principles (database transactions, schema, communication) are well-articulated with explicit checkmarks and warnings. The constraint against cross-context transactions is particularly important and well-justified.


48-62: Glossary removes ambiguity and aids clarity.

Defining "Modularization," "Package," "Bounded context," "Cross-cutting concern," "Vertical slice," "Ubiquitous language," and "Table prefix" upfront is excellent. This is especially helpful given the technical nature of the document and potential confusion with Go module terminology.


102-139: Folder structure rationale is sound and enables boundary enforcement.

The three-level hierarchy (root activity/, activity/service/, activity/mysql/) is well-motivated. The separation clearly enables architectural checks to prevent unauthorized imports of internal packages (service/ and mysql/ private to the context), which directly supports the bounded context goals.


140-163: Database naming strategy is pragmatic and extensible.

The choice of table prefixes (activity_past, activity_upcoming, etc.) with descriptive suffixes is clearer than alternatives and anticipates future expansion. Aligns well with GitLab's established pattern and industry standards.


165-176: Architecture enforcement plan is concrete and leverages existing patterns.

Referencing server/archtest (used by Android MDM) and proposing arch_test.go in the Activity context keeps tooling consistent and lowers adoption friction. The specific import restrictions are clear and enforceable.


182-217: Communication pattern clearly establishes Activity as a write-only cross-cutting concern.

The Mermaid diagram and dependency description effectively communicate that other contexts write through activity.NewActivity() without reading back. This asymmetric dependency is well-justified as an event-sourcing approach for audit trails.


219-295: Consequences section provides balanced analysis with explicit trade-offs.

The benefits, drawbacks, and impact sections are realistic. Particularly strong is the transparent acknowledgment in the drawbacks (lines 247–253) that audit-only scope may be too small to justify a separate bounded context—this demonstrates clear-eyed thinking about the pilot's limitations and accepted risks.


299-352: Alternatives considered are thorough and well-reasoned.

All three alternatives are presented with detailed pros, cons, and explicit reasoning for rejection:

  • Alt 1 (status quo): Acknowledges short-term benefits but correctly identifies long-term risk.
  • Alt 2 (larger context like vulnerabilities): Recognizes higher business value but explains why smaller pilot is more prudent.
  • Alt 3 (shared datastore): Clearly articulates how it would undermine bounded context isolation and references GitLab's success with full-stack ownership.

This demonstrates architectural maturity.


367-377: Success criteria are specific and time-bounded.

Seven measurable success criteria with a 2-week evaluation window provide clear checkpoint for decision-making (continue, refine, or pivot). The criteria cover both technical aspects (boundaries, migration, performance) and organizational factors (developer experience, documentation).


1-10: ADR metadata is complete and follows template.

Status, Date, and heading are properly formatted. The "Proposed" status is appropriate for a new ADR under review.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@getvictor thanks again for putting this together! +1 on starting with activity module. Is there a place where you can expand a bit on how this will integrate into our current Fleet service? It looks like @jahzielv's code is merged to main which does this by creating a new activity module in serve.go and injecting it into the Android service; is this the general pattern you're envisioning? We've talked about ways to streamline/automate this sort of dependency injection in the future, especially as the dependency graph grows, but I'm fine starting with a manual approach and iterating.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there are some DI libraries for Go out there, but I think we're better off rolling any DI tooling on our own. Those libs tend to either

  • Be hard to integrate into an existing codebase, or
  • Hide a ton of the implementation details

If we write our own, we can iterate, integrate it slowly, and only build what we need when we need it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sgress454 Yes, the activity module will have an interface that will be injected into the current Fleet service (and any other services that need it). We're not explicitly trying to improve DI in this ADR. But there is value in having a standard/cleaner DI approach that would make our integration tests easier.


**Database transactions:**
- ✅ **Exclusive write ownership**: Each bounded context owns writing to its tables exclusively (no other context writes to them)
- ✅ **Reads allowed across contexts**: Other contexts can read via joins for queries, but this indicates coupling

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this indicates coupling - I think we should avoid coupling between modules, even for read. If a module needs data from another context, it should ideally get it through that module’s public abstraction.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, normal reads should go through the bounded context interface. However, sometimes we have transactions that join other tables, so we're not explicitly preventing DB reads that join tables from other bounded contexts.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think for now, we have to live the world of "one big pot".

I think we should avoid those cross-boundaries JOINS if possible, but it's also going to be a while until we get to a place where they are not necessary.

@mostlikelee mostlikelee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important to discuss how this approach will affect us organizationally. With each team owning multiple modules, we will undoubtedly need to collaborate more cross-team as a given feature will likely cross multiple modules. Further utilizing working groups may address this.

@jahzielv

jahzielv commented Nov 19, 2025

Copy link
Copy Markdown
Contributor

Some important context: we need this change because the nature of what we're building has changed.

We're no longer building a read-only osquery wrapper: we're building a distributed system that manages very complex state and enables very complex actions to be taken across hundreds of thousands of devices.

To build that system, we need an updated approach to our architecture.

This change is critical because it will allow us to better serve customers and build the best device management product out there.

@getvictor

Copy link
Copy Markdown
Member Author

I think it's important to discuss how this approach will affect us organizationally. With each team owning multiple modules, we will undoubtedly need to collaborate more cross-team as a given feature will likely cross multiple modules. Further utilizing working groups may address this.

I actually think the opposite. With bounded contexts, a bug fix or a story should actually be touching few (ideally 1) bounded context on average. If we have most of our PRs touching multiple bounded contexts, then this is a red flag.

@jahzielv

jahzielv commented Nov 20, 2025

Copy link
Copy Markdown
Contributor

I think it's important to discuss how this approach will affect us organizationally. With each team owning multiple modules, we will undoubtedly need to collaborate more cross-team as a given feature will likely cross multiple modules. Further utilizing working groups may address this.

I actually think the opposite. With bounded contexts, a bug fix or a story should actually be touching few (ideally 1) bounded context on average. If we have most of our PRs touching multiple bounded contexts, then this is a red flag.

@getvictor I see what you're saying! I agree with Tim as well though: organizationally we have to agree to build the product with DDD and bounded contexts in order to maximize the impact of this decision.

One tactical organizational step we can take: tightly defining each team's responsibilities.

With a very detailed (always subject to change ofc!) list of domains for each team, we can then identify the red flags you call out. Right now it'd be very hard to do that, because organizationally we spread work of all kinds across all the teams.


### Industry examples of modular monolith architecture

**GitLab** (Ruby on Rails, 2.2M+ LOC): Successfully transitioned from monolith to modular architecture. Their [modular monolith documentation](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/modular_monolith) outlines key motivations: avoiding microservices complexity, maintaining code approachability, and enabling better feature boundaries through DDD. As [GitLab CEO stated](https://about.gitlab.com/blog/why-were-sticking-with-ruby-on-rails/#monorails): "A well-structured, well-architected, highly modular program that runs as a single process."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great structure, and GitLab's example of how to transition successfully and transparently.

@lukeheath

Copy link
Copy Markdown
Member

@getvictor Thank you, as always, for the thoughtful ADR. I appreciate you continuing to drive this initiative forward and make it a reality. Great resource you found in GitLab's docs! Also very applicable to where we're at.

@sgress454 @mostlikelee @jahzielv Thanks for your feedback. The ADR process yields better results when we consider it from many angles. I think what some comments are getting at is discussed in GitLab's docs:

While we do recognize that modularization is a significant technical endeavor, we believe that the main challenge is organizational, rather than technical. We not only need to design separation in a way that modules are decoupled in a pragmatic way which works well on GitLab.com but also on self-managed instances, but we need to align modularization with the way in which we want to work at GitLab.

Is that accurate? I think it's salient because if all teams are working on all features, they'll all be working in all domains, which would create a lot of coordination overhead. This separation is much more effective when specific teams own specific domains. Our goal going into 2026 is for that to be the case, which started with defining areas of expertise. We plan to extend that such that we're better planning our roadmap to the capacities of the individual teams. If more capacity is needed, we'll grow the product group's capacity that is responsible for that domain.

If we want to benefit from those results, we need to invest in getting there incrementally and iteratively. Great call out from @jahzielv:

One tactical organizational step we can take: tightly defining each team's responsibilities.

Since we already have the areas of expertise defined, we could expand it with more detail. A sheet would probably be the best place to work that out. As we modularize the service layer, we could introduce ownership of each service, with the goal of directing all future work in that service to that group. Of course getting there will be sticky and require more cross-team communication, but that's an investment worth making.

Anyway just wanted to check in on this. I still need to review the ADR in depth, and I'd like to read through GitLab's docs as well. Planning to provide full review before EOD Wednesday.

@jahzielv

Copy link
Copy Markdown
Contributor

While we do recognize that modularization is a significant technical endeavor, we believe that the main challenge is organizational, rather than technical. We not only need to design separation in a way that modules are decoupled in a pragmatic way which works well on GitLab.com but also on self-managed instances, but we need to align modularization with the way in which we want to work at GitLab.

@lukeheath this was the biggest takeaway from that doc for me! I think it's 100% accurate and relates to where we are today.

Our architectural decisions ultimately have to align with and support our workflows as a team and the product we want to build.

@lukeheath lukeheath left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor tweaks, but I agree with this ADR in theory. However, I'm unclear what kind of changes are necessary at the organization level to implement this change. For example:

  1. Who owns the new Activity bounded context?
  2. Does only that group touch that context, or can other product groups touch it if it's reviewed by the group that owns it?
  3. How do we decide and document which bounded contexts are owned by which groups? The answers to 1 and 2 above will inform this decision.

I'm not sure how much of that information needs to be captured in the ADR vs. elsewhere, but I'd like to know what changes are necessary to make this a reality before it's merged. To me, it looks like this:

  1. File, draft, and estimate an eng-initited story to make this change.
  2. Update the handbook to document established bounded contexts and who owns it. Are any process changes needed? If one group owns the context, how do we make sure any stories touching it get routed to them? Do any activity changes need to be spec'd as their own story?
  3. We update CODEOWNERS so any changes to an owned bounded context require review from the established DRIs for that. This could also serve as the document for who owns which bounded contexts.

Once we've clarified what operational changes are needed I'm supportive of approving and merging this ADR. I just want to make sure we know what it will take to get traction across the org.

Also, because @lucasmrod is back online tomorrow, I'd like to get his review and thoughts before merging. In order for this to be successful long-term, we'll need buy-in from the engineers working in the affected code so everyone is building towards the same goal. To that end, I want to make sure we're considering it from all angles and addressing any concerns.

Comment thread docs/Contributing/adr/0007-pilot-activity-bounded-context.md Outdated

**Database schema:**
- ✅ **Single shared database**: All contexts share one MySQL database for operational simplicity (no separate databases per context)
- ⚠️ **Cross-context joins allowed but scrutinized**: Joins across bounded context tables are permitted for read operations (queries, reports, dashboards), but indicate coupling and should receive extra scrutiny during code review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we make sure this happens, and all engineers know? Update PR template?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The simplest approach is to add it to PR template.

A more sophisticated approach would be to run a check in CI that looks for DB table names mentioned in the wrong bounded context. We could then, for example, maintain a list of allowed cross-context joins and require that an entry be added to that list if a new JOIN (or table mention) was added.

Co-authored-by: Luke Heath <luke@fleetdm.com>
@getvictor

Copy link
Copy Markdown
Member Author

Some minor tweaks, but I agree with this ADR in theory. However, I'm unclear what kind of changes are necessary at the organization level to implement this change. For example:

  1. Who owns the new Activity bounded context?

I recommend the team that does this work to own the new Activity bounded context. (dogfooding)

  1. Does only that group touch that context, or can other product groups touch it if it's reviewed by the group that owns it?

Yes, per our values, anyone can touch the new bounded context. Ownership will be determined via handbook and/or CODEOWNERS. Yes, changes should be reviewed by the team that owns it.

  1. How do we decide and document which bounded contexts are owned by which groups? The answers to 1 and 2 above will inform this decision.

I'm not sure how much of that information needs to be captured in the ADR vs. elsewhere, but I'd like to know what changes are necessary to make this a reality before it's merged. To me, it looks like this:

  1. File, draft, and estimate an eng-initited story to make this change.

Yes. This is our normal process.

  1. Update the handbook to document established bounded contexts and who owns it. Are any process changes needed? If one group owns the context, how do we make sure any stories touching it get routed to them? Do any activity changes need to be spec'd as their own story?

Yes, handbooking the ownership is a good idea. In general, once engineers/product/managers are familiar with the bounded contexts that we have, it should be obvious (hopefully) to everyone which bounded context(s) the story belongs in. Just like it is fairly obvious which of today's product groups the story belongs to.

Activity changes should NOT need their own story. The plan for this ADR is for the other contexts to own the activity details (what activities they source). The activity context should take an interface and support common functionality on all activities. That said, if we're adding brand new functionality/fields to activities, then it should be a new story or subtask.

  1. We update CODEOWNERS so any changes to an owned bounded context require review from the established DRIs for that. This could also serve as the document for who owns which bounded contexts.

Yes, this makes sense.

Once we've clarified what operational changes are needed I'm supportive of approving and merging this ADR. I just want to make sure we know what it will take to get traction across the org.

Also, because @lucasmrod is back online tomorrow, I'd like to get his review and thoughts before merging. In order for this to be successful long-term, we'll need buy-in from the engineers working in the affected code so everyone is building towards the same goal. To that end, I want to make sure we're considering it from all angles and addressing any concerns.

@MagnusHJensen MagnusHJensen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall a very nice read, hoping this gets approved and excited to learn more.


Following GitLab's bounded context pattern, each context owns its full stack including data access:

```text

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to understand where would the Module/Bounded context service interface live?

Inside the bounded context package or at a shared top-level? I don't think it's super clear from the read.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Go community, it is considered idiomatic to define the interface with the consumer. This means:

  • consumers (other bounded contexts) only declare the methods they need
  • different consumers can define different interfaces around a common concrete type based on their needs

I'd like to try following this pattern since it fits best with a modular architecture. Although in this case, there will be methods that everyone will want, so it makes sense to have those methods in a shared interface at the top level.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the pilot goes well, we'll create a handbook page for the modular monolith that documents accepted conventions similar to GitLab's (though not as exhaustive with our resources.)

lucasmrod
lucasmrod previously approved these changes Nov 30, 2025

@lucasmrod lucasmrod left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. It is a low-risk pilot and worth doing it before taking the bounded context idea to the next level with bigger components/features (e.g. vulnerabilities).

Just a few thoughts:

  • How about calling it what it is: audit or activity_audit(instead of activity_past).
  • Other contexts cannot import server/activity/service or server/activity/mysql (private implementation: Usually the way to signal "private" in Go is to use internal directories (server/activity/internal/*).
  • Database table prefix seems like a simple and clear idea for these bounded contexts.
  • Re: https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/audit-logs.md. Currently the developer needs to add new activities to ActivityDetailsList. We could take the chance to improve this moving forward (or to keep things simple, just put all these types into a separate automatic_doc package or so).

@getvictor

Copy link
Copy Markdown
Member Author

LGTM. It is a low-risk pilot and worth doing it before taking the bounded context idea to the next level with bigger components/features (e.g. vulnerabilities).

Just a few thoughts:

  • How about calling it what it is: audit or activity_audit(instead of activity_past).

Although audit is indeed a more accurate term, no one actually calls it that at Fleet. Most engineers and others call it Activity. The product labels the section as Activity on the dashboard. So, we should also call it activity until product decides to call it something else.

  • Other contexts cannot import server/activity/service or server/activity/mysql (private implementation: Usually the way to signal "private" in Go is to use internal directories (server/activity/internal/*).

@lucasmrod Great idea to use internal. I think we need more architectural boundaries/checks like this. The only issue is that it creates a need for a wiring layer at the activity bounded context, since serve.go cannot directly instantiate the datastore. But perhaps that's better. https://github.com/fleetdm/fleet/pull/36454/files#diff-d3f5d0102791ab40f30af4cc0144f07acf81a4c7343d0a01cc467a1a538ec0cd

Yes, makes sense. I think this is a bit outside the scope, but we can try to make this improvement.

@getvictor

Copy link
Copy Markdown
Member Author

@lukeheath, assuming this ADR is approved, I have the first (of several) implementation PR up for review: #36454

@jahzielv

jahzielv commented Dec 1, 2025

Copy link
Copy Markdown
Contributor

However, I'm unclear what kind of changes are necessary at the organization level to implement this change.

@lukeheath one tactical organizational change I'd suggest would be to have a synchronous eng + product training on domain driven design. I think it'd be really helpful for us all to be on the same page on this and have a ubiquitous language around this approach to product development.

@getvictor
getvictor requested a review from lukeheath December 1, 2025 18:53
@getvictor

Copy link
Copy Markdown
Member Author

@jahzielv

Your modules approach is great for organizing service-layer code, and I think we should keep doing that. ADR-0007 is about something different: it's about proving we can extract a true bounded context with enforced boundaries.

The immediate value isn't in the activity feature itself. It's in establishing the pattern and discovering what coupling we need to break. For example, ADR-0001 (Android service) started down this path but didn't fully decouple generic logic from domain logic. This ADR is about doing that separation properly so we have a template for larger extractions.

Once we've done one bounded context end-to-end, we'll have a clearer path for extracting higher-value contexts. Identity (User, Session) is a candidate for the next extraction since it's a cross-cutting concern like activities, which makes it a natural fit for separation.

Adding/separating modules inside a bounded context is complementary work. It's cleaning up the service layer, which makes future bounded context extraction easier.

@lukeheath

Copy link
Copy Markdown
Member

Thanks all, great updates. Will review ASAP this week. Looks like we have a good plan for implementing it.

**Database transactions:**
- ✅ **Exclusive write ownership**: Each bounded context owns writing to its tables exclusively (no other context writes to them)
- ✅ **Reads allowed across contexts**: Other contexts can read via joins for queries, but this indicates coupling
- ❌ **DB transactions DO NOT cross bounded context boundaries**: Each transaction must be scoped to a single context's tables to avoid distributed transaction complexity

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know what this would look like for other parts of the software? It feels like it would require radically changing our DB schema to allow for more potential inconsistency.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upcoming activities would be a good thought exercise given the expectations that code and the queue have around transactions that span the upcoming activities table + a number of other tables(software installs, script executions, VPP and possibly soon MDM commands)

@getvictor getvictor Dec 3, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that upcoming activities is a good thought exercise and I haven't thought about it in detail. It may be the most complex situation we have. The 2 potential bounded contexts on my radar as easier follow up ADRs are identity (Users) and vulnerabilities.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would require radically changing our DB schema

Let's not do this part until we hit Fleet 5.

- ⚠️ **Cross-context joins allowed but scrutinized**: Joins across bounded context tables are permitted for read operations (queries, reports, dashboards), but indicate coupling and should receive extra scrutiny during code review

**Bounded context communication:**
- ✅ **Public service APIs only**: Bounded contexts expose public service layer methods (e.g., `ListActivities()`); internal implementation details and datastore methods remain private to the context

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have Product review this? Since they ultimately design our APIs we at least need them to be aware of this as it may need to inform certain API design decisions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JordanMontgomery great callout!!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, our handlers (controllers) are closely tied to the service. So, we are tying the API to our service calls, often a 1-for-1 match. With bounded contexts, some API calls may need to orchestrate calls to multiple services.

I was thinking that perhaps that orchestration logic should be in its own controller layer (above service). Just an idea now. I haven't tried it.

If that's the case, the API would be somewhat decoupled from service layer methods.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, any API changes (or changes to any interface) would need to go through product drafting. We'll be approaching implementation as an engineering-initiated story that's drafted and estimated, so we can hash out more specifics in that issue before it's implemented.

@jahzielv

jahzielv commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

If we do find that DDD is a helpful tool for us and this POC is successful, what are next steps to incorporate DDD into our product development/drafting flow?

@lukeheath

Copy link
Copy Markdown
Member

@jahzielv

If we do find that DDD is a helpful tool for us and this POC is successful, what are next steps to incorporate DDD into our product development/drafting flow?

If the pilot is successful, we'll likely engage a DDD coach/consultant. Someone with an outsider's perspective that can help educate us on DDD thinking, but also help us find our way through the codebase's hall of mirrors.

@getvictor Really appreciate you being a driving force for this. The need for modularization is something we've all recognized for awhile, but it takes someone to invest energy into turning that sentiment into action.

I'm approving this ADR as a pilot with the following agreement:

  1. This is a two-way door, and we're willing to pay the price to back it out later.
  2. We'll define an engineering-initiated story and take it through drafting and estimation.
  3. We'll share architectural design decisions with everyone in engineering and invite feedback.
  4. We'll get approval from all tech leads on all related PRs before merging into main.

If the pilot is successful, we'll formulate another ADR to define record our decision to adopt it throughout the codebase, and define an implementation approach.

Comment thread docs/Contributing/adr/0007-pilot-activity-bounded-context.md Outdated

@lukeheath lukeheath left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@getvictor Let's work on fleshing out the eng-initiated story on Monday 🙌

@lukeheath
lukeheath merged commit b106229 into main Dec 5, 2025
6 checks passed
@lukeheath
lukeheath deleted the activity-bounded-ctx branch December 5, 2025 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants