minderlabs / demo

Minder Demo App
1 stars 0 forks source link

References/Links #32

Open richburdon opened 7 years ago

richburdon commented 7 years ago

Consider the following Project->Task structure (ignoring for the moment the possibility of tasks belonging to multiple projects).

type Project implements Item {
  team: Group!
}

type Group implements Item {
  members: [User]
}

type Task implements Item {
  project: Project
}

[OPTIMIZATION ISSUE: If we just want to retrieve the project ID, can we do this in the resolver without having to retrieve the Project record (we already have the ID value in the Task record -- should we proactively parse the query shape to determine if the actual record should be retrieved?) Is this moot once we have links?]

In the ProjectCard, we could do this:

query ProjectQuery($itemId: ID!) {
  item(itemId: $itemId) {
    team {
      members {
        tasks(filter: { expr: { field: "project", value: $itemId } }) {
          title
        }
      }
    }
  }
}

And/or implement Project->Task hierarchies (aka Item "compositions") via parent->child references.

type Project implements Item {
  team: Group!
  tasks: [Task]!
}

And filter these by member/assignee in the renderer.

At some point we'll also need to be able to reference links in the filter.

adamberenzweig commented 7 years ago

Does ignoring the possibility of tasks belonging to multiple projects simplify anything? If not we should solve the more general many-to-many problem.

For posterity, I'll enumerate the 4 types of composition we discussed the other day:

  1. Structured inlining.
  2. Reference by key.
  3. Link as first-order relation object.
  4. Query embedded in parent.

The schema you wrote above represents the relationship directly (2), but the query you've sketched is more like (4).

For discussion here's an approach that builds on top of a relational model underneath (imagine the resolvers use sql tables underneath to represent the relationships between projects, tasks and users):

type Item {
  ...
  links: [Item]
}

type Task implements Item {
  assignee: User
  owner: User
}

type Project implements Item {
  // nothing special here... all relations will be links.
}
query projectQuery($itemId: ID!) {
  item(itemId: $itemId) {
    links {
      ... on Task {
        assignee: { ... UserFragment }
        owner: { ... UserFragment }
      }
    }
  }
}

When the client wants to build a page that groups tasks by assignee, it has to sort them into user buckets at the client -- the query response is not structured that way. But the shape of the query is simple and general.

During resolution, first the server would fetch a project by ID and retrieve the item keys for all links, then as it went down the response tree each linked item would be resolved in turn.

Any other problems w/ this approach?

richburdon commented 7 years ago

Let's give examples for the four cases:

  1. Structured inlining: these are not first-order items. E.g., A Contact has 3 email addresses (which are potentially schema types). A Calendar event has 3 inline tasks (that could be "snapped off" into first-order items at some point.
  2. Reference by key: A user has a Contact item (which is the aggregated form of contact records (inlined as above) from multiple external sources (FB, Google, LinkedIn)
  3. First order links: a) bi-directional many-to-many (e.g., project->user) where we want to be able to efficiently navigate in either direction; b) cases where we need to store metadata on the link itself (e.g., ACL).
  4. Embedded query: here I meant that the time has specific semantics (e.g., Event has participants and observers). Or that it might have "context" specific items (e.g., "important tasks" based on location, time, etc.)

I think Schema is very powerful for the following reasons (relative to a homogenous system):

I don't understand your SQL analogy. Typically SQL have type-specific tables and references. I don't think the different models really affect the backend implementation.

However...

Against schema:

I think you client side sorting "problem" isn't relevant: you can still do nesting in the query if that's useful to you. It won't be as clear to traverse (e.g., items.items.items.items) and I'm not sure what the traversal syntax would be for "members" vs "participants" things.

I think in summary the trade-off are: For Schema: clarity, constraints, simplicity, contextual matching (i.e., not expressed purely by filter). Against: Flexibility, possibility of client side holy grail caching (offline).

richburdon commented 7 years ago

My comment about making resolvers automatic still would have issues. There is always going to be some opaqueness in the queries (context, ACL, the current concept of refs for nested queries, etc.) So for offline we will need to some extent a shallow implementation of the resolvers. I think of these as stored procedures.

Let's compare some real life queries side by side.

adamberenzweig commented 7 years ago

Agreed, assignee vs owner is a good canonical example of the need for some type-specific schema.

Agreed that using schema with nice field names to capture relationships makes deep nested queries easier to write, e.g. project.tasks.assignee.name instead of something with a bunch of filters.

For the record here's some issues that I believe motivate this discussion:

  1. Ability to nest projects, infinitely
  2. Ability to capture many-to-many relationships
  3. Ability to add notes, tasks, documents (search results), and other projects to a project.
  4. Easily moving items between projects

Some questions related to the links between Projects and People:

  1. Are the links between projects and people also used to compute ACLs? (related to #24 )
  2. If I follow a project, can others see that fact? (proposal: Yes, if others can see the project)
  3. What determines the set of people for whom we show task sections on a card? a) explicit "team" field pointing to a Group b) implicit set of all people assigned tasks that are linked to this project, c) set of all followers linked to this project. (Proposal: foo)

Worth noting two issues w a very generic approach that we identified when chatting:

  1. Heterogeneous pagination, e.g. paginating separately through tasks vs notes attached to a project. But we can handle with multiple nested queries with different filters, something supported by graphql. e.g.:

    item(itemid: $itemId) {
    notes: links(type=Note, offset=...) { NoteFragment }
    tasks: links(type=Task, offset=...) { TaskFragment }
    }
  2. The assignee vs owner problem (same as your "participant vs observer" example). Ie. differentiating by pure link semantics that are not captured anywhere else in the object data. The only generic solution that comes to mind is first-class Link objects with a type field -- like freebase subject, predicate, object triples.

My comment about SQL was really about where the relationship is expressed: In our current approach, the interesting relationships are represented by embedded queries (type 4 in our typology) that the client needs to express. E.g.

tasks(filter: { expr: { field: "project", value: $itemId } }) or tasks(filter: { expr: { field: "assignee", ref: "id" } } AND project=this (paraphrasing)

that feels awkward to me -- the relationship between tasks and the parent project should already be known, not expressed again in the filter of an inner query that the client has to write.

richburdon commented 7 years ago

Consider:

Event => Project(label=eng) => Task(status=blocking)

Query:

{
  project(label: eng) {
    tasks(status: blocking)
  }
}

{
  links(type:Project, label:eng) {
    ... on Project { ... }
    links(type:Task, status:blocking) {
      ... on Task { 
        assignee: { 
          name 
        } 
      }
    }
  }
}

Issues:

adamberenzweig commented 7 years ago
type Project {
  notes(PaginationFilter): [Note]
  tasks(PaginationFilter): [Task]
  projects(PaginationFilter): [Project]
  documents(PaginationFilter): [Document]
}

vs


type Project {
  links(FilterInput): [Item]
}
richburdon commented 7 years ago

Or:

type Item {
  links(relation, filter)
}
adamberenzweig commented 7 years ago

Also see: https://docs.google.com/document/d/1qMwaBb8jcip1zZipvFIM5SZDDE07ZL5Lpz0UtM1dH-I/edit#heading=h.8b19te3y0jvi