![]()
TL;DR
- This post is not an argument against foreign keys.
- The real questions are:
- Is
Rolea pure constant or operational data? - What do we reference — name or immutable code?
- Who is responsible for data integrity — the database or the application?
- Is
- In CheckUS, the most balanced solution was:
keep
Rolein the database, but reference it only by immutable codes using soft references.
The Question That Started It All
Does
CampusRolereally need aparentRole?
And if it does, should that relationship be enforced with a foreign key?
This was the question that kept coming up while designing the authorization system for CheckUS.
The system defines global base roles:
TEACHERSTUDENTADMIN
Each campus can then define custom roles:
- “Senior Teacher”
- “Assistant Teacher”
- “Trial Student”
A custom role is always derived from a base role and can only select a subset of that base role’s permissions.
“Senior Teacher” is still a kind of TEACHER.
What Does CampusRole Represent?
@Entity
public class CampusRole {
@Id
private Long id;
private String name; // "Senior Teacher"
@Column(name = "parent_role", nullable = false)
private String parentRole; // "TEACHER"
}
The interesting part is that parentRole is not a foreign key.
It’s just a String.
At first glance, this looks dangerous.
“Aren’t we giving up database-level integrity?”
Yes — intentionally.
Misconception: parentRole Is for Permission Checks
It’s not.
At read time (authorization), we only care about CampusRolePermission.
List<Permission> permissions =
campusRolePermissionRepository.findByCampusRoleId(campusRoleId);
No joins.
No need to load Role.
This is not about LAZY vs EAGER fetching or query optimization.
Where parentRole Actually Matters
parentRole is critical during writes, not reads.
// UserCampusRoleService.assignCampusRole()
Role parentRole = roleRepository.findByName(campusRole.getParentRole())
.orElseThrow(() -> new BusinessException("Parent role not found"));
When assigning a custom role (“Senior Teacher”) to a user, the system must ensure:
- Which base role this custom role belongs to
- Whether the user already has that base role (
TEACHER) - If not, assign the base role first, then attach the custom role
So parentRole is not about permissions.
It defines the lineage of the role.
Then Shouldn’t This Be a Foreign Key?
That’s a fair question.
@ManyToOne
@JoinColumn(name = "parent_role_id")
private Role parentRole;
Advantages of Foreign Keys
- Database-enforced referential integrity
- Safe deletes and updates
- Renaming a role does not break references (PK-based)
But Also Trade-offs
- Strong coupling between
CampusRoleandRole - Two conceptually different domains become tightly bound at the DB level
- Less flexibility as the system evolves
So we chose a soft reference.
The Real Problem with Soft References
Here’s the key insight:
Soft references are not the problem. Referencing mutable values is.
// ❌ Dangerous
private String parentRole; // "TEACHER"
name is mutable.
People change it.
Product requirements change it.
Rebranding changes it.
And when it changes, everything breaks.
The Critical Failure Scenario: Renaming a Role
UPDATE role SET name = 'INSTRUCTOR' WHERE name = 'TEACHER';
The database is perfectly happy.
The application is not.
roleRepository.findByName("TEACHER")
.orElseThrow(...)
This is the true risk of name-based soft references.
Do We Abandon Soft References?
No.
We change what we reference.
Core Rule
Soft references must always point to immutable identifiers.
// ✅ Safe
private String parentRoleCode; // "ROLE_TEACHER"
codeis internal and immutable- Renaming is forbidden
- Changes happen via add + migrate, never rename
Display names live separately.
Role {
String code; // ROLE_TEACHER (immutable)
String displayName; // Teacher / 강사 (mutable)
boolean active;
}
Then Why Not Remove the Role Table Entirely?
A very reasonable question.
When Code/Enum Is Enough
You can remove the Role table entirely if all of these are true:
- The set of roles will never grow
- Role policies and permissions never change
- No enable/disable requirements
- No localization or metadata
- Deploying for every change is acceptable
In that case, roles are just constants.
Reality: Roles Tend to Become Operational Data
Over time, systems usually need:
- Temporary role deactivation (
active = false) - Localized display names
- Permission presets
- UI grouping and ordering
- Audit logs
- Multi-tenant customization
At that point, roles are no longer constants. They are operational data.
That’s why keeping Role in the database makes sense.
The Two Viable Options
Option A: Code-Only Roles (Enum-Based)
- No
Roletable - All references are enum codes
- Very simple
- Low operational flexibility
Option B: Role Table + Immutable Codes (CheckUS Choice)
Roleremains operational datacodeis immutable and uniquedisplayNameis mutable- All references use
code, notname
This removes rename-related risks while keeping flexibility.
“Isn’t This Basically the Same as a Foreign Key?”
It may look similar, but it’s not.
| Aspect | Foreign Key | Soft Reference (Code) |
|---|---|---|
| Integrity enforced by | Database | Application + process |
| Rename safety | High | High (via immutability) |
| DB-level features | Cascades, joins | None |
| Domain coupling | Strong | Loose |
The difference is not what you reference, but who is responsible.
How to Represent This in ERD
- Physical ERD: No relationship line (no foreign key)
- Logical ERD: Dashed line (logical reference)
In the diagram above:
- Dashed arrow: Soft reference (no FK constraint)
- parentRoleCode → code: Referenced by String value
- Physically independent but logically connected
Final Decision (CheckUS)
- Keep
Rolein the database - Treat it as operational data
- Reference only immutable
codevalues - Avoid
namein any reference - Keep existing FK usage where already present (e.g.,
UserCampusRole)
Conclusion
The real design question was never:
“Foreign key or not?”
It was:
What should be immutable, and who should enforce integrity?
In CheckUS, soft references backed by immutable codes provided the best balance between:
- safety
- flexibility
- and long-term maintainability
Soft references are not a replacement for foreign keys. They are a shift in responsibility.
If you are ready to own that responsibility, soft references can absolutely survive in production.
Comments