A newer version of the Ed-Fi ODS / API is now available. See the Ed-Fi Technology Version Index for a link to the latest version.

API Resource Reference Handling Changes for Unified Keys

Introduction

The Ed-Fi Standard uses a data modeling approach that uses composite primary keys consisting of natural domain values (as opposed to one that uses surrogate keys). The primary benefit of this approach is that deep referential integrity is enforced by the database engine rather than by the application.

Consider this example involving the Grade entity:

The use of composite keys ensures that the SchoolId associated with the GradingPeriod that is referenced by the Grade is the same SchoolId used by the StudentSectionAssociation. This modeling concept is known as a unified key.

References

The Ed-Fi ODS API represents each foreign key relationship as a reference, as you can see in the following sample Grade response (note the gradingPeriodReference and the studentSectionAssociationReference):

{
  "id": "6556959b93dc4990a8140e137e6219ec",
  "gradingPeriodReference": {
    "gradingPeriodDescriptor": "uri://ed-fi.org/GradingPeriodDescriptor#First Six Weeks",
    "periodSequence": 1,
    "schoolId": 255901001,
    "schoolYear": 2011,
    "link": {
      "rel": "GradingPeriod",
      "href": "/ed-fi/gradingPeriods/77be2d9a60fe4e2582fa5345502bd891"
    }
  },
  "studentSectionAssociationReference": {
    "beginDate": "2010-08-23T00:00:00Z",
    "localCourseCode": "ALG-1",
    "schoolId": 255901001,
    "schoolYear": 2011,
    "sectionIdentifier": "25590100102Trad220ALG112011",
    "sessionName": "2010-2011 Fall Semester",
    "studentUniqueId": "604822",
    "link": {
      "rel": "StudentSectionAssociation",
      "href": "/ed-fi/studentSectionAssociations/e691f0e87e32433a979dc84f5aefd338"
    }
  },
  "gradeTypeDescriptor": "uri://ed-fi.org/GradeTypeDescriptor#Grading Period",
  "numericGradeEarned": 63,
  "learningStandardGrades": [
    {
      "learningStandardReference": {
        "learningStandardId": "111.32.NA.A.1.D",
        "link": {
          "rel": "LearningStandard",
          "href": "/ed-fi/learningStandards/e7733d52c70f47ff99a60c7f82266530"
        }
      },
      "numericGradeEarned": 63
    }
  ]
}

You might also notice that both references contain a schoolId  property, yet the Grade entity only stores a single value. In previous versions of the Ed-Fi ODS API, since both references are required, only the "first" SchoolId value was used and the other(s) were just dropped. Internally, there was determinate logic as to which source value was used and which were discarded, but the result was that an API client could supply different values across the unifying references and not receive an error (so long as all the properties on the required references contained non-default values).

Based on feedback from the community, we are introducing a new validation rule that will report on these discrepancies and return an error in the response instead of quietly discarding the additional values. For the example payload above, if we changed either of the SchoolIds to another value we'll now get a 400 Bad Request  response similar to the following:

{
  "message": "Validation of 'GradePost' failed.\n\tSupplied values for unified key property 'schoolId' on 'Grade' are not consistent: gradingPeriodReference.schoolId = 255901107, studentSectionAssociationReference.schoolId = 255901001\n"
}

... or ...

{
  "message": "Validation of 'GradePost' failed.\n\tSupplied values for unified key property 'schoolId' on 'Grade' are not consistent: gradingPeriodReference.schoolId = 255901001, studentSectionAssociationReference.schoolId = 255901107\n"
}

Previously, the API would accept the "first" value and perform the update.

Context-Specific References

In scenarios where key unification occurs on a child class of a resource with a property that is defined on the parent, the API had built a context-specific reference class that excluded the properties flowing down from the parent. Consider the ClassPeriod reference on the BellScheduleClassPeriod (a child class in the BellSchedule resource):

In this scenario, the child class experience key unification on the SchoolId, but the SchoolId is already established by the parent (BellSchedule). In previous versions of the API, no SchoolId property was generated on this context-specific reference, and so if the SchoolId was supplied in the JSON, it was quietly dropped during deserialization. Further clouding the issue, the Open API metadata produced for this class showed SchoolId as being part of that reference, though the value was never used (or as discussed below, returned from a GET response).

To address this, there are two changes being made to the API.

First, with the new validation rule (discussed earlier), the SchoolId in the reference will be validated if it is provided. As such, the following POST request (with differing SchoolIds) to /ed-fi/bellSchedules:

{
  "schoolReference": {
    "schoolId": 255901001
  },
  "bellScheduleName": "Normal Schedule",
  "endTime": "16:00:00",
  "startTime": "08:15:00",
  "totalInstructionalTime": 325,
  "classPeriods": [
    {
      "classPeriodReference": {
        "classPeriodName": "01 - Traditional",
        "schoolId": 255901107
      }
    },
    ...

... will result in the following 400 Bad Request  response:

{
  "message": "Validation of 'BellSchedulePost' failed.\n\tSupplied values for unified key property 'schoolId' on 'BellScheduleClassPeriod' are not consistent: schoolId (from parent context) = 255901001, classPeriodReference.schoolId = 255901107\n"
}

However, for backwards compatibility (for this specific scenario), if the reference is only partially supplied, the missing contextual value(s) will be automatically inferred from the parent. For example, the following POST request body will not result in an error:

{
  "schoolReference": {
    "schoolId": 255901001
  },
  "bellScheduleName": "Normal Schedule",
  "endTime": "16:00:00",
  "startTime": "08:15:00",
  "totalInstructionalTime": 325,
  "classPeriods": [
    {
      "classPeriodReference": {
        "classPeriodName": "01 - Traditional"
      }
    },
    ...

This behavior is now considered deprecated. In the future, this accommodation will be removed and all references will be required to be fully formed.

Context-Specific Reference Serialization

With the addition of the contextual properties on the "context-specific" references, there will be a minor change to the response body of GET requests. Whereas previously, these contextual property values were not returned in the reference, they will now be included.

Previous versions of the API would return this response for a GET request to /ed-fi/bellSchedules (note that the classPeriodReference does not include the SchoolId):

{
  "id": "37d190a4db4a4a699304f2a0d18914a4",
  "schoolReference": {
    "schoolId": 255901001,
    "link": {
      "rel": "School",
      "href": "/ed-fi/schools/1a3a89dc27d14d01a14b36cbfd0da713"
    }
  },
  "bellScheduleName": "Normal Schedule",
  "endTime": "16:00:00",
  "startTime": "08:15:00",
  "totalInstructionalTime": 325,
  "classPeriods": [
    {
      "classPeriodReference": {
        "classPeriodName": "01 - Traditional",
        "link": {
          "rel": "ClassPeriod",
          "href": "/ed-fi/classPeriods/454be1eb719142e2a53da30012df0f71"
        }
      }
    },

With the changes to normalize all reference handling, the response will now include the contextual values in these references:

{
  "id": "37d190a4db4a4a699304f2a0d18914a4",
  "schoolReference": {
    "schoolId": 255901001,
    "link": {
      "rel": "School",
      "href": "/ed-fi/schools/1a3a89dc27d14d01a14b36cbfd0da713"
    }
  },
  "bellScheduleName": "Normal Schedule",
  "endTime": "16:00:00",
  "startTime": "08:15:00",
  "totalInstructionalTime": 325,
  "classPeriods": [
    {
      "classPeriodReference": {
        "schoolId": 255901001,
        "classPeriodName": "01 - Traditional",
        "link": {
          "rel": "ClassPeriod",
          "href": "/ed-fi/classPeriods/454be1eb719142e2a53da30012df0f71"
        }
      }
    },

Future API Behavior of References

As we look to improve validation messaging and refine the API for consistency, it is likely that in future versions of the API that all required references will have special validation applied, and that all properties on references will also be required (including the "context-specific" references).

For example, when a required reference isn't supplied at all, we'll likely produce the following validation error response:

{
  "message": "The request is invalid.",
  "modelState": {
    "request.GradingPeriodReference": [
      "The GradingPeriodReference field is required."
    ]
  }
}

When a reference isn't fully formed (it is missing properties or some of the properties are assigned default values), we'll likely produce the following validation error response:

{
  "message": "The request is invalid.",
  "modelState": {
    "request.CalendarReference.SchoolId": [
      "SchoolId is required."
    ]
  }
}