Teams
Can someone help me understand the value of the Team
type?
Current model
type Team {
id: ID!
created: DateTime!
updated: DateTime
members: [TeamMember]
role: String
objectId: Object
objectType: String
}
type TeamMember {
user: User
status: String
alias: Alias
}
type Alias {
name: Name
email: Email
aff: String
}
I have a few problems with this:
-
Hard to grok.
- I've had to explain several times to new developers on the project (multiple times to the same people in some cases) that a
Team
does not model the team of people associated with an object but only the people of a specific role. - Most teams (at least in
elife-xpub
) have a single member which goes against the natural usage of the term.
- I've had to explain several times to new developers on the project (multiple times to the same people in some cases) that a
-
Overhead in managing teams in the code
- When you want to add a member, you first need to check if there's an existing team of the appropriate role and create one if not
- When removing a member you need to check if the resulting team is empty and if so delete it
- Does code need to handle cases like having two teams of the same type for the same object?
-
All fields on
TeamMember
are optional because- Not all team members will have a
user
(e.g. a suggested reviewer added by email may not have an associated user until they log in) - Not all team members will have a
status
(e.g. it's only for modelling reviewer invitations AFAICR) - Not all team members will have an
alias
(e.g. an editor assigned to a manuscript doesn't need an alias)
- Not all team members will have a
-
There's a bunch of other properties that need to be associated with team members and these depend on the role:
- reviewers need invitation dates, reminders sent etc
- authors need their corresponding author, rank etc.
- eLife editors need their eLife person ID
We can model these extra properties as a union type like
union TeamMemberMeta = ReviewerMemberMeta | EditorMemberMeta | AuthorMemberMeta
. In Apollo server you have to write aresolveType
function for each union type and interface which, when passed a data object, returns the type of that object. You can try and guess the type from the combination of properties (e.g. if it has acorresponding
property then it must be anAuthorMemberMeta
) but it's complicated and it doesn't work if the properties are optional, or you can store the type along with the object.If we take the more reliable route and store the type of team member meta then we're essentially duplicating the role: once on the team and again on each team member's meta. What happens if we have a team of authors with some members having only editor member meta?
Suggestions
- Merge (the as yet non-existent)
TeamMemberMeta
intoTeamMember
so it becomes a union type or interface. This means we can havestatus
andalias
only when it makes sense. - Move
role
up intoTeamMember
and use this inresolveType
as the type discriminator
Intermediate model
type Team {
id: ID!
created: DateTime!
updated: DateTime
members: [TeamMember]
- role: String
objectId: Object
objectType: String
}
-type TeamMember {
+interface TeamMember {
+ role: String!
user: User
- status: String
- alias: Alias
}
+type ReviewerMember implements TeamMember {
+ role: String!
+ user: User
+ status: String!
+ alias: Alias
+ reminders: [Date]
+}
type Alias {
name: Name
email: Email
aff: String
}
+type AuthorMember implements TeamMember {
+ #...
+}
#...
This means we now only have one team per entity and that team holds all team members of any role.
Having arrived here, I notice that Team
no longer has any meaningful properties. It's just a way of linking members to an object. So we can replace the entity with an interface:
Suggested model
type Manuscript implements TeamObject {
#...
+ members: [TeamMember]
}
-type Team {
+interface TeamObject {
- id: ID!
- created: DateTime!
- updated: DateTime
members: [TeamMember]
- objectId: Object
- objectType: String
}
interface TeamMember {
role: String!
}
type ReviewerMember implements TeamMember {
role: String!
user: User
status: String!
alias: Alias
reminders: [Date]
}
type Alias {
name: Name
email: Email
aff: String
}
type AuthorMember implements TeamMember {
#...
}
#...