At a glance
| Product | Happy5 Performance, a multi-tenant enterprise OKR and performance-management SaaS |
|---|---|
| My role | Backend engineer, sole owner of the integration's domain layer |
| Duration | Eight months |
| Stack | Ruby on Rails, Jira REST API, Sidekiq, ActionCable, PostgreSQL |
| Still running | The core design is still the production code path four years later |
The one-sentence version
I designed the layer that decides what a Jira project means inside an OKR platform: how issues become goals, how workflow states become measurable progress, how Jira's people become accountable owners.
The problem
"Integrate with Jira" sounds like an afternoon of REST calls. The demo is easy: pick a project, pick a goal, draw a line between them.
The reality is that an OKR platform and an issue tracker disagree about almost everything they model: hierarchy, progress, ownership, and who's actually in charge. The integration is where those disagreements get resolved into a single number a director looks at in a quarterly review.
flowchart LR
subgraph JIRA["Jira Cloud"]
J1["Project → Epic → Task →<br/>Subtask (3 levels below project)"]
J2["Arbitrary workflow statuses<br/>'In Review', 'QA', 'Blocked'…"]
J3["Lead, Administrator, Member,<br/>Viewer, Assignee, Reporter, Watcher"]
J4["Jira is the source of truth<br/>…or is it?"]
end
subgraph H5["Happy5 Performance"]
H1["Project → Task<br/>(2 levels, hard validation)"]
H2["A percentage.<br/>Rolled up from child records<br/>in our own database."]
H3["Leader, Assignee, Reviewer,<br/>PMO, Stakeholder<br/>(drives approval + review flows)"]
H4["Customers each have<br/>a different opinion"]
end
J1 -.->|"hierarchy<br/>boundary"| H1
J2 -.->|"progress<br/>boundary"| H2
J3 -.->|"identity + role<br/>boundary"| H3
J4 -.->|"authority<br/>boundary"| H4
How it's built
The integration is a Rails service sitting between the two systems. Jira Cloud pushes updates through webhooks and exposes a REST API for pulling project and issue data. A background layer built on Sidekiq does the actual translation, mapping Jira's projects, issues and workflow states into Happy5's goals, tasks and ownership, so a sync never blocks a web request the user is waiting on. Once a sync finishes, ActionCable pushes the result to the browser live, and everything lands in PostgreSQL alongside the rest of the platform's data.
sequenceDiagram
participant U as User
participant API as Rails API
participant Q as Sidekiq
participant J as Jira REST
participant WS as ActionCable
U->>API: Link a Jira project
API->>API: validate & persist the link
API-->>U: 202 Accepted
Note over U,API: request ends here
API->>Q: enqueue sync job
Q->>J: fetch project / issue data
J-->>Q: payload
Q->>Q: map Jira data into OKR goals
Q->>Q: apply tenant configuration
Q->>Q: persist changes
Q->>Q: enqueue roll-up (progress) job
Q->>Q: import sub-items (if requested)
Q->>WS: broadcast result
WS-->>U: UI updates live
Linking a real project used to run synchronously: fetch the project, its roles, every issue, every issue's watchers, then write it all back. Fine in a demo, far too slow against a large tenant. Moving it off the request path bought speed, but it also meant a lesson worth keeping: async buys you latency, not correctness. The steps above have to run in a strict order, or a child item can get created before its parent has finished syncing. Making something async is a design decision with its own failure modes, not a free performance win.
Key takeaways
- Translate a foreign system's state into your own domain in one isolated layer. Don't let a partner's concepts leak into abstractions the rest of your platform depends on.
- When two systems model identity or ownership differently, define an explicit precedence order rather than a simple lookup table.
- Let customers configure where they disagree with your defaults, instead of forking behavior per client.
- Async trades latency for an ordering guarantee you now have to design and enforce yourself.
Backend engineer on Happy5 Performance, 2021 to present.