Skip to content

docs: add ADR for taxonomy_type field, amend ADR 0002#662

Open
mgwozdz-unicon wants to merge 1 commit into
openedx:mainfrom
mgwozdz-unicon:cbe-taxonomy-type-field-adr
Open

docs: add ADR for taxonomy_type field, amend ADR 0002#662
mgwozdz-unicon wants to merge 1 commit into
openedx:mainfrom
mgwozdz-unicon:cbe-taxonomy-type-field-adr

Conversation

@mgwozdz-unicon

Copy link
Copy Markdown
Contributor

Summary

openedx_tagging's taxonomy Get endpoints need to report whether a taxonomy is a
Competency Taxonomy (#618), symmetrically with the Create/Import endpoint's
taxonomy_type field (#614). Two approaches were tried on #618 and found unsound:

  • Checking for a related CompetencyTaxonomy row directly inside openedx_tagging
    (hasattr(instance, "competencytaxonomy")) — hardcodes a downstream applet's
    multi-table-inheritance relation name into a generic, standalone library.
  • An overridable Taxonomy.get_type() method mirroring the existing system_defined
    base/override pattern — unsound for two independent reasons: that pattern
    (Taxonomy._taxonomy_class / .cast()) is being removed per Remove Taxonomy subclasses functionality + system-level taxonomies #634, and separately,
    .cast()/.copy()'s existing implementation only copies a hardcoded list of base
    Taxonomy fields in Python and never queries a subclass's own table, so it would
    silently misrepresent a true multi-table-inheritance subclass like CompetencyTaxonomy
    even if Remove Taxonomy subclasses functionality + system-level taxonomies #634 weren't happening.

This PR adds ADR 0013 (openedx_tagging), proposing a taxonomy_type field
directly on Taxonomy, set explicitly by the caller that creates a CompetencyTaxonomy
row rather than inspected by openedx_tagging itself. It also amends ADR 0002
(openedx_learning) with the corresponding creation-time invariant and a note that its
existing rejection of a flat taxonomy_type column stands, unaffected by this narrower
field (different problem: a REST API type label, not where competency data lives).

Both are Status: Proposed, open for review.

Context

openedx_tagging needs a layering-safe way to report whether a Taxonomy
is competency-enabled (openedx#618), without gaining any knowledge of the
CompetencyTaxonomy subclass CBE owns. Adds a taxonomy_type field to
the base Taxonomy model set by the caller that creates a
CompetencyTaxonomy row, and documents why two prior approaches on
openedx#618 (an openedx_tagging-side hasattr check, an overridable get_type()
method) were unsound. Amends ADR 0002 to note the corresponding
creation-time invariant and that its rejection of a flat taxonomy_type
column still stands, unaffected by this narrower field.
@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Jul 16, 2026
@openedx-webhooks

Copy link
Copy Markdown

Thanks for the pull request, @mgwozdz-unicon!

This repository is currently maintained by @axim-engineering.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

whether a ``CompetencyTaxonomy`` row actually exists for that taxonomy. This is accepted
because it is enforced at a single transactional chokepoint (the creation path for a
``CompetencyTaxonomy``), not as an ongoing invariant that application code must maintain
across multiple call sites.

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'm not sure about this.

Add a taxonomy_type field directly to Taxonomy: A TaxonomyType(models.TextChoices) enum with values TAGS (default) and COMPETENCY

So we're still hard-coding a reference to COMPETENCY in the tagging app, and we now have to ensure that we keep the field's value in sync with whether or not a CompetencyTaxonomy actually exists.

Plus, per the ADR 614, we have to update a whole bunch of other tagging REST APIs to support the taxonomy_type field.


If we're going to hard-code some reference to Competencies in the tagging app, I think what I'm leaning toward is something radically simpler:

Put this in the taxonomy list/get serializer, and that's it - no other changes needed to the taxonomy app.

# Technically this is a layering violation as our tagging/taxonomy app is not supposed
# to be aware of competencies, but it simplifies a lot for us and it doesn't require
# importing any competency code.
# We can revisit this in the future if we introduce other taxonomy types.
taxonomy_type = "competency" if hasattr(instance, "competencytaxonomy") else "tags"

REST APIs: I don't think there's any need to change the tagging REST APIs. To create a competency taxonomy from the frontend, you could either (A) call the "create taxonomy" REST API and on succes, call the "create competency metadata for taxonomy" REST API (from the cbe app), or (B) call a competency REST API that creates the Taxonomy and the CompetencyTaxonomy object together in a single request.

Export: You could add the taxonomy_type field to the JSON export data if that seems useful. It's not possible to include on CSVs.

Import: If the user chooses to import something as a Competency taxonomy (in the frontend UI), it's the same thing: import the taxonomy then call a second API to convert it to Competency.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Going through in order:

On the sync-drift point: yes, I agree that this tradeoff is the risk, but hopefully it can be resolved at implementation time.

On the REST API surface per #614: I don't think ADR 13 is adding work there. Those endpoints need to accept taxonomy_type on create/import regardless of how the Get side reports it, so that's #614's scope either way, not something this ADR introduces on top.

On the hasattr suggestion: I hear the appeal, it's less code today, and I get that you're framing it as a deliberate, called-out layering violation rather than an accidental one. Thinking it through more, I don't think either approach avoids touching openedx_tagging when a new type shows up eventually, the field approach still means adding a value to the shared enum. Where I think they differ is what that touch requires: with the field, adding a type is adding a string to a catalog, the read path (TaxonomySerializer) doesn't change, it already just returns whatever's stored. With hasattr, adding a type means adding another branch to the serializer's logic that names a specific downstream app's MTI relation, so the read path itself keeps growing a fact about another app's model internals with every flavor. That feels like the thing the ADR is trying to keep out of a generic library, holding the type as a piece of data the caller owns, versus holding it as code that inspects other apps' structure. Also worth flagging: this looks close to option 2 from your comment on #618 (Adding a tiny bit of Competency awareness to the base tagging app), where you said you leaned toward 1+3 over it. Did something change your thinking there, or is this meant as a variant of option 1 rather than 2?

On separate Competency REST APIs: Since the Taxonomies landing page is meant to serve both flows, I don't think it makes sense to make the frontend juggle two calls, especially when this could become even more calls than that if the number of taxonomy types increases.

On export/import: agreed, that's already settled in #614 and not really in scope here.

@bradenmacdonald bradenmacdonald Jul 17, 2026

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, sorry, I'm still not happy with any of the approaches here so I'm just throwing out ideas to see if there's anything that you like. I'm not so unhappy that I'm going to block anything though. I'm reluctantly fine with any of these approaches, but I want to see if we can come up with something clean that doesn't have any layering violations.

Thinking about this more, what if we kept the taxonomy code in openedx-core / oel_tagging completely free of competency awareness, but use the platform's override of the REST API at openedx/core/djangoapps/content_tagging/rest_api/v1/ to inject the competency pieces you need in the REST API? Then you could add taxonomy_type = "competency" if hasattr(instance, "competencytaxonomy") else "tags" to the serializer and also wire through the "competency creation" changes to the REST API specified in #614 while still keeping some semblance of layer separation, because the core tagging API is unchanged.

So the layers would be:

  • oel_tagging python API & REST APIs in this repo - pure tagging/taxonomy only ✅
  • content_tagging python API in openedx-platform - pure tagging/taxonomy only ✅ (as specified on [BE] Update taxonomy Create & Import endpoints to accept Taxonomy Type #614 , "Layering constraint. openedx_tagging/api.py::create_taxonomy() creates only a plain Taxonomy. It must not import or call anything from openedx_learning. The CompetencyTaxonomy creation belongs exclusively in the CBE applet.")
  • CBE django app python API - can convert taxonomies to competencies, provide other competency features ✅ respects layering
  • content_tagging REST API in openedx-platform (openedx/core/djangoapps/content_tagging/rest_api/v1/) - injects and handles the taxonomy_type field (not persisted in the database), which gets routed to the competency python API as needed. ⚠️ breaks layering but this is a fairly minimal "wrapper"/"router" type code anyways so it seems like a logical place to do this
  • Studio MFE frontend: combined handling of taxonomies & competencies as per UX mockups. ⚠️ breaks layering but the product folks think it's better UX than separating them into two separate UIs.

At least in that case there are clean boundaries drawn in several places that completely respect the layering.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

open-source-contribution PR author is not from Axim or 2U

Projects

Status: Needs Triage

Development

Successfully merging this pull request may close these issues.

3 participants