🧘‍♀️
HL7 FHIR Verson 5
Lý Thuyết
Lý Thuyết
  • Welcome
  • 🛂Introduction to HL7 R5
    • Tổng quan về FHIR R5
  • Nguyên tắc thiết kế FHIR
  • Lịch sử🚀FHIR đến R5
  • 🤷RESTful API & FHIR
    • REST Fundamentals
  • HTTP & FHIR REST API
  • Content Negotiation FHIR
  • 👨‍💼FHIR Resource Model and Architecture
    • FHIR R5 Model Resources
  • FHIR R5 Resource Classification
  • New FHIR R5 Resources
  • Exporing FHIR R5 Servers
  • 🏓FHIR R5 Search & CRUD
    • CRUD Operations in FHIR R5
  • Search in FHIR R5
  • Bundles & Transactions in FHIR R5
  • Operations in FHIR R5
  • 🧘‍♀️FHIR R5 DATA STRUCTURE
    • Data Types In FHIR R5
  • Extensions & ElementDefinition
  • Metadata & Control Elements
  • Narrative & Text
  • 🛏️Deep Dive into Resource FHIR R5
    • Clinical Resources in R5
  • Administrative Resources
  • Specialized Resources
  • Infrastructure Resources
  • 🎎FHIR R5 Profiling & Validation
    • Conformance Resources in R5
  • Creating & Use FHIR R5 Profiles
  • FHIRPath & FluentPath FHIR R5
  • Validation in FHIR R5
  • Implementation Guides in R5
  • 🤦‍♂️FHIR R5 Operations & Messaging
    • Operations R5 Updates
  • FHIR Messaging in R5
  • Event-based Communication In FHIR R5
  • FHIR Documents In R5
  • GraphQL FHIR R5
  • 🔐Security và Privacy In FHIR R5
    • FHIR Security trong R5
  • Consent & Data Segmentation In FHIR R5
  • Provenance & Audit
  • FHIR Data Security
  • 🧑‍💻Terminology In FHIR R5
    • CodeSystem & ValueSet
  • Terminology Operations
  • Terminology Service
  • Terminology Bindings
Powered by GitBook
On this page
  • 1. GraphQL FHIR Schema
  • 2. Implementing GraphQL Server for R5
  • 3. Complex Queries
  • 4. Mutations
  • Kết luận
  • Tài nguyên tham khảo

GraphQL FHIR R5

GraphQL là một ngôn ngữ truy vấn và thao tác dữ liệu được phát triển bởi Facebook (nay là Meta), cung cấp một cách tiếp cận linh hoạt và hiệu quả hơn so với REST truyền thống. Khi kết hợp với FHIR R5, GraphQL mang lại nhiều lợi ích đáng kể:

  • Lấy chính xác dữ liệu cần thiết: Client có thể chỉ định chính xác những trường dữ liệu họ cần

  • Giảm số lượng request: Có thể lấy nhiều tài nguyên liên quan trong một request duy nhất

  • Giảm overload dữ liệu: Không còn tình trạng nhận quá nhiều dữ liệu không cần thiết

  • Tài liệu tự động: Schema GraphQL tự tài liệu hóa API

  • Type-safe: Kiểm tra kiểu dữ liệu tĩnh

Trong bài viết này, chúng ta sẽ đi sâu vào việc triển khai GraphQL với FHIR R5, xây dựng schema, thực hiện truy vấn phức tạp, thực hiện mutations, và xem xét các vấn đề về hiệu suất.

1. GraphQL FHIR Schema

Cấu trúc cơ bản của FHIR GraphQL Schema

Schema GraphQL cho FHIR định nghĩa cách mà các tài nguyên FHIR được ánh xạ thành các kiểu dữ liệu và truy vấn GraphQL. HL7 cung cấp các hướng dẫn chính thức về cách ánh xạ này, nhưng chúng ta sẽ xem xét cấu trúc cơ bản.

Ánh xạ Resource Types

Mỗi Resource Type trong FHIR được ánh xạ thành một GraphQL Type tương ứng:

type Patient {
  id: ID
  meta: Meta
  identifier: [Identifier]
  active: Boolean
  name: [HumanName]
  telecom: [ContactPoint]
  gender: AdministrativeGender
  birthDate: Date
  address: [Address]
  # ... các trường khác
}

type Observation {
  id: ID
  meta: Meta
  identifier: [Identifier]
  status: ObservationStatus
  code: CodeableConcept
  subject: Reference
  encounter: Reference
  effectiveDateTime: DateTime
  valueQuantity: Quantity
  # ... các trường khác
}

Ánh xạ Data Types

Các Data Type của FHIR cũng được ánh xạ thành các GraphQL Type:

type HumanName {
  use: NameUse
  text: String
  family: String
  given: [String]
  prefix: [String]
  suffix: [String]
  period: Period
}

type Identifier {
  use: IdentifierUse
  type: CodeableConcept
  system: String
  value: String
  period: Period
  assigner: Reference
}

type CodeableConcept {
  coding: [Coding]
  text: String
}

Ánh xạp Enum Types

Các giá trị cố định trong FHIR được ánh xạ thành GraphQL Enum:

enum AdministrativeGender {
  male
  female
  other
  unknown
}

enum ObservationStatus {
  registered
  preliminary
  final
  amended
  corrected
  cancelled
  entered_in_error
  unknown
}

Root Query Type

GraphQL FHIR Schema định nghĩa một Root Query Type chứa các entry point để truy vấn từng loại tài nguyên:

type Query {
  Patient(id: ID): Patient
  PatientList(
    _filter: String
    _sort: String
    _count: Int
    _offset: Int
  ): PatientConnection
  
  Observation(id: ID): Observation
  ObservationList(
    _filter: String
    _sort: String
    _count: Int
    _offset: Int
    subject: Reference
    code: CodeableConcept
    date: String
  ): ObservationConnection
  
  # ... các tài nguyên khác
}

Connection Types cho Phân trang

FHIR GraphQL sử dụng mô hình "Connections" để phân trang kết quả:

type PatientConnection {
  count: Int
  offset: Int
  pageSize: Int
  first: PatientEdge
  last: PatientEdge
  edges: [PatientEdge]
}

type PatientEdge {
  mode: String
  score: Float
  resource: Patient
}

Những thay đổi trong Schema R5

FHIR R5 có một số thay đổi đáng chú ý trong GraphQL Schema:

  1. Hỗ trợ tốt hơn cho các element mới: Schema đã được cập nhật để phản ánh các element mới trong FHIR R5.

  2. Cải thiện Reference Resolution: Khả năng phân giải tham chiếu đã được cải thiện, cho phép truy vấn sâu hơn qua các tài nguyên liên kết.

  3. Hỗ trợ Search Parameters mới: R5 có nhiều search parameter mới được ánh xạ thành các argument trong GraphQL.

  4. Type Safety nâng cao: R5 cải thiện khả năng kiểm tra kiểu dữ liệu trong GraphQL schema.

Tạo và Tùy chỉnh Schema

Để tạo GraphQL Schema cho FHIR R5, chúng ta có thể sử dụng các thư viện có sẵn hoặc tự xây dựng từ đặc tả FHIR:

Sử dụng thư viện GraphQL-FHIR

Có nhiều thư viện mã nguồn mở hỗ trợ tạo GraphQL Schema từ FHIR, ví dụ như graphql-fhir cho Node.js:

const { FHIRSchema } = require('graphql-fhir');

// Tạo schema từ đặc tả FHIR R5
const fhirSchema = FHIRSchema.build({
  version: '5.0.0',
  customScalars: true,
  customResolvers: {
    Patient: {
      // Custom resolver cho trường observations trong Patient
      observations: async (parent, args, context) => {
        const client = context.getFHIRClient();
        return client.search({
          resourceType: 'Observation',
          searchParams: {
            subject: `Patient/${parent.id}`
          }
        });
      }
    }
  }
});

Tự tạo Schema từ StructureDefinition

Một cách khác là phân tích các StructureDefinition của FHIR để tạo Schema:

const fs = require('fs');
const { buildSchema } = require('graphql');

// Đọc StructureDefinition của FHIR R5
const structureDefs = JSON.parse(fs.readFileSync('fhir-r5-definitions.json', 'utf8'));

// Chuyển đổi thành GraphQL Schema types
let schemaTypes = '';

structureDefs.forEach(def => {
  if (def.resourceType === 'StructureDefinition' && def.kind === 'resource') {
    schemaTypes += `type ${def.name} {\n`;
    
    // Xử lý các element của resource
    def.snapshot.element.forEach(element => {
      // Tạo field cho element
      // Phức tạp trong thực tế, đây chỉ là code minh họa
    });
    
    schemaTypes += '}\n\n';
  }
});

// Xây dựng schema
const schema = buildSchema(schemaTypes);

2. Implementing GraphQL Server for R5

Kiến trúc Server GraphQL cho FHIR

Một server GraphQL cho FHIR R5 thường có các thành phần sau:

  1. GraphQL Schema: Định nghĩa cấu trúc của API

  2. Resolvers: Xử lý logic lấy dữ liệu cho mỗi trường

  3. FHIR Client: Kết nối với FHIR server để lấy và ghi dữ liệu

  4. Authentication/Authorization: Xác thực và phân quyền

  5. Caching Layer: Lưu cache dữ liệu để tối ưu hiệu suất

Ví dụ triển khai với Node.js và Apollo Server

Sau đây là ví dụ triển khai GraphQL server đơn giản cho FHIR R5 sử dụng Node.js và Apollo Server:

const { ApolloServer } = require('apollo-server');
const { FHIRSchema } = require('graphql-fhir');
const axios = require('axios');

// Tạo FHIR client đơn giản
const fhirClient = {
  read: async (resourceType, id) => {
    const response = await axios.get(`https://r5.fhir-server.org/${resourceType}/${id}`);
    return response.data;
  },
  search: async (resourceType, params) => {
    const queryString = new URLSearchParams(params).toString();
    const response = await axios.get(`https://r5.fhir-server.org/${resourceType}?${queryString}`);
    return response.data;
  },
  // Thêm các phương thức khác (create, update, delete...)
};

// Tạo schema FHIR
const fhirSchema = FHIRSchema.build({
  version: '5.0.0',
});

// Tạo Apollo Server
const server = new ApolloServer({
  schema: fhirSchema,
  context: () => ({
    getFHIRClient: () => fhirClient
  })
});

// Khởi động server
server.listen().then(({ url }) => {
  console.log(`🚀 FHIR GraphQL server ready at ${url}`);
});

Triển khai Custom Resolvers

Resolvers là hàm xử lý để lấy dữ liệu cho mỗi trường trong schema. Dưới đây là ví dụ chi tiết hơn về cách triển khai resolvers:

const resolvers = {
  Patient: {
    // Resolver để lấy danh sách Observation của bệnh nhân
    observations: async (patient, args, context) => {
      const client = context.getFHIRClient();
      const bundle = await client.search('Observation', {
        subject: `Patient/${patient.id}`,
        _count: args._count || 10,
        _sort: args._sort || '-date'
      });
      
      return {
        count: bundle.total,
        pageSize: args._count || 10,
        edges: bundle.entry.map(entry => ({
          resource: entry.resource
        }))
      };
    },
    
    // Resolver để lấy danh sách Condition của bệnh nhân
    conditions: async (patient, args, context) => {
      const client = context.getFHIRClient();
      const bundle = await client.search('Condition', {
        subject: `Patient/${patient.id}`
      });
      
      return {
        count: bundle.total,
        edges: bundle.entry.map(entry => ({
          resource: entry.resource
        }))
      };
    }
  },
  
  Observation: {
    // Resolver để lấy thông tin chi tiết về bệnh nhân từ subject reference
    subject: async (observation, args, context) => {
      if (!observation.subject || !observation.subject.reference) {
        return null;
      }
      
      const [resourceType, id] = observation.subject.reference.split('/');
      if (resourceType !== 'Patient') {
        return null;
      }
      
      const client = context.getFHIRClient();
      return client.read('Patient', id);
    }
  },
  
  Query: {
    // Root resolver để lấy bệnh nhân theo ID
    Patient: async (_, { id }, context) => {
      const client = context.getFHIRClient();
      return client.read('Patient', id);
    },
    
    // Root resolver để tìm kiếm bệnh nhân
    PatientList: async (_, args, context) => {
      const client = context.getFHIRClient();
      const bundle = await client.search('Patient', {
        _count: args._count || 10,
        _sort: args._sort,
        _offset: args._offset || 0,
        ...args  // Các tham số tìm kiếm khác
      });
      
      return {
        count: bundle.total,
        offset: args._offset || 0,
        pageSize: args._count || 10,
        edges: bundle.entry.map(entry => ({
          resource: entry.resource
        }))
      };
    }
  }
};

Xử lý Authentication và Authorization

Bảo mật là vấn đề quan trọng khi triển khai GraphQL cho dữ liệu y tế. Dưới đây là cách triển khai SMART on FHIR authentication với GraphQL:

const { ApolloServer } = require('apollo-server-express');
const express = require('express');
const jwt = require('jsonwebtoken');

// Middleware xác thực SMART on FHIR
const smartAuthMiddleware = async (req, res, next) => {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    req.user = null;
    return next();
  }
  
  const token = authHeader.split(' ')[1];
  
  try {
    // Xác thực JWT token (trong thực tế cần xác thực với SMART Auth server)
    const decoded = jwt.verify(token, 'your-secret-key');
    req.user = decoded;
    
    // Kiểm tra các scopes
    req.scopes = decoded.scope.split(' ');
  } catch (error) {
    req.user = null;
  }
  
  next();
};

// Tạo Express app và áp dụng middleware
const app = express();
app.use(smartAuthMiddleware);

// Tạo Apollo Server
const server = new ApolloServer({
  schema: fhirSchema,
  context: ({ req }) => {
    // Truyền thông tin người dùng và scopes vào context
    return {
      user: req.user,
      scopes: req.scopes || [],
      getFHIRClient: () => createFHIRClient(req.headers.authorization)
    };
  }
});

// Áp dụng middleware của Apollo cho Express
server.applyMiddleware({ app });

// Khởi động server
app.listen({ port: 4000 }, () => {
  console.log(`🚀 FHIR GraphQL server ready at http://localhost:4000${server.graphqlPath}`);
});

3. Complex Queries

GraphQL cho phép thực hiện các truy vấn phức tạp, lấy nhiều tài nguyên liên quan trong một request duy nhất. Dưới đây là một số ví dụ:

Lấy thông tin bệnh nhân và các kết quả xét nghiệm

query PatientWithObservations($patientId: ID!) {
  Patient(id: $patientId) {
    id
    name {
      given
      family
    }
    birthDate
    gender
    observations {
      count
      edges {
        resource {
          id
          code {
            coding {
              system
              code
              display
            }
          }
          effectiveDateTime
          valueQuantity {
            value
            unit
          }
          status
        }
      }
    }
  }
}

Tìm kiếm bệnh nhân và lấy thông tin toàn diện

query SearchPatientsWithDetails($searchTerm: String) {
  PatientList(_filter: $searchTerm, _count: 5) {
    count
    edges {
      resource {
        id
        name {
          given
          family
        }
        birthDate
        gender
        address {
          line
          city
          state
          postalCode
        }
        telecom {
          system
          value
          use
        }
        conditions {
          edges {
            resource {
              id
              clinicalStatus {
                coding {
                  code
                  display
                }
              }
              code {
                coding {
                  system
                  code
                  display
                }
                text
              }
              onsetDateTime
            }
          }
        }
        medications: medicationRequests {
          edges {
            resource {
              id
              status
              intent
              medicationCodeableConcept {
                coding {
                  system
                  code
                  display
                }
                text
              }
              dosageInstruction {
                text
                timing {
                  repeat {
                    frequency
                    period
                    periodUnit
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Truy vấn chẩn đoán và kết quả xét nghiệm liên quan

query DiagnosisWithRelatedData($conditionId: ID!) {
  Condition(id: $conditionId) {
    id
    code {
      coding {
        system
        code
        display
      }
      text
    }
    subject {
      ... on Patient {
        id
        name {
          given
          family
        }
      }
    }
    evidence {
      code {
        coding {
          system
          code
          display
        }
      }
      detail {
        ... on Observation {
          id
          status
          code {
            coding {
              display
            }
          }
          valueQuantity {
            value
            unit
          }
        }
      }
    }
    relatedObservations: observations {
      edges {
        resource {
          id
          code {
            coding {
              display
            }
          }
          valueQuantity {
            value
            unit
          }
          effectiveDateTime
        }
      }
    }
  }
}

Truy vấn lịch sử thuốc và phản ứng thuốc

query MedicationHistoryWithReactions($patientId: ID!) {
  Patient(id: $patientId) {
    id
    name {
      given
      family
    }
    medicationRequests: medicationRequests {
      edges {
        resource {
          id
          status
          medicationCodeableConcept {
            coding {
              system
              code
              display
            }
          }
          authoredOn
        }
      }
    }
    medicationStatements: medicationStatements {
      edges {
        resource {
          id
          status
          medicationCodeableConcept {
            coding {
              display
            }
          }
          effectiveDateTime
        }
      }
    }
    allergyIntolerances {
      edges {
        resource {
          id
          clinicalStatus {
            coding {
              display
            }
          }
          type
          category
          criticality
          code {
            coding {
              display
            }
          }
          reaction {
            manifestation {
              coding {
                display
              }
            }
            severity
          }
        }
      }
    }
  }
}

Các kỹ thuật truy vấn nâng cao

Fragments

Fragments là cách tái sử dụng một phần truy vấn:

fragment PatientBasics on Patient {
  id
  name {
    given
    family
  }
  birthDate
  gender
}

fragment ObservationDetails on Observation {
  id
  status
  code {
    coding {
      system
      code
      display
    }
  }
  effectiveDateTime
  valueQuantity {
    value
    unit
  }
}

query GetPatientAndObservations($patientId: ID!) {
  Patient(id: $patientId) {
    ...PatientBasics
    observations {
      edges {
        resource {
          ...ObservationDetails
        }
      }
    }
  }
}

Variables và Directives

Sử dụng variables và directives để tạo truy vấn động:

query GetPatient($id: ID!, $includeObservations: Boolean = false, $includeConditions: Boolean = false) {
  Patient(id: $id) {
    id
    name {
      given
      family
    }
    birthDate
    
    observations @include(if: $includeObservations) {
      edges {
        resource {
          id
          code {
            coding {
              display
            }
          }
          valueQuantity {
            value
            unit
          }
        }
      }
    }
    
    conditions @include(if: $includeConditions) {
      edges {
        resource {
          id
          code {
            coding {
              display
            }
          }
          clinicalStatus {
            coding {
              code
            }
          }
        }
      }
    }
  }
}

4. Mutations

Mutations trong GraphQL FHIR cho phép tạo, cập nhật và xóa tài nguyên. FHIR R5 GraphQL cung cấp các mutations tương ứng với các hoạt động CRUD.

Định nghĩa Mutations trong Schema

type Mutation {
  # Tạo tài nguyên mới
  PatientCreate(resource: PatientInput!): PatientCreatePayload
  
  # Cập nhật tài nguyên
  PatientUpdate(id: ID!, resource: PatientInput!): PatientUpdatePayload
  
  # Xóa tài nguyên
  PatientDelete(id: ID!): PatientDeletePayload
  
  # Các mutations khác cho các tài nguyên khác
  ObservationCreate(resource: ObservationInput!): ObservationCreatePayload
  ObservationUpdate(id: ID!, resource: ObservationInput!): ObservationUpdatePayload
  ObservationDelete(id: ID!): ObservationDeletePayload
  
  # ... v.v.
}

# Input types cho mutations
input PatientInput {
  identifier: [IdentifierInput]
  active: Boolean
  name: [HumanNameInput]
  telecom: [ContactPointInput]
  gender: AdministrativeGender
  birthDate: Date
  address: [AddressInput]
  # ... các trường khác
}

# Output types cho mutations
type PatientCreatePayload {
  resource: Patient
  error: OperationOutcome
}

type PatientUpdatePayload {
  resource: Patient
  error: OperationOutcome
}

type PatientDeletePayload {
  resource: Patient
  error: OperationOutcome
}

Tạo bệnh nhân mới

mutation CreateNewPatient($patientData: PatientInput!) {
  PatientCreate(resource: $patientData) {
    resource {
      id
      name {
        given
        family
      }
    }
    error {
      issue {
        severity
        code
        diagnostics
      }
    }
  }
}

Với variables:

{
  "patientData": {
    "name": [
      {
        "given": ["Nguyễn", "Văn"],
        "family": "A"
      }
    ],
    "gender": "male",
    "birthDate": "1990-01-15",
    "address": [
      {
        "line": ["123 Đường ABC"],
        "city": "Hà Nội",
        "postalCode": "100000",
        "country": "Việt Nam"
      }
    ],
    "telecom": [
      {
        "system": "phone",
        "value": "+84901234567",
        "use": "mobile"
      }
    ]
  }
}

Cập nhật thông tin bệnh nhân

mutation UpdatePatient($id: ID!, $patientData: PatientInput!) {
  PatientUpdate(id: $id, resource: $patientData) {
    resource {
      id
      name {
        given
        family
      }
      telecom {
        system
        value
      }
    }
    error {
      issue {
        severity
        code
        diagnostics
      }
    }
  }
}

Ghi kết quả xét nghiệm mới

mutation CreateObservation($observationData: ObservationInput!) {
  ObservationCreate(resource: $observationData) {
    resource {
      id
      status
      code {
        coding {
          system
          code
          display
        }
      }
      subject {
        reference
      }
      valueQuantity {
        value
        unit
        system
        code
      }
    }
    error {
      issue {
        severity
        diagnostics
      }
    }
  }
}

Với variables:

{
  "observationData": {
    "status": "final",
    "code": {
      "coding": [
        {
          "system": "http://loinc.org",
          "code": "15074-8",
          "display": "Glucose [Moles/volume] in Blood"
        }
      ]
    },
    "subject": {
      "reference": "Patient/123456"
    },
    "effectiveDateTime": "2023-08-15T08:30:00+07:00",
    "valueQuantity": {
      "value": 5.8,
      "unit": "mmol/L",
      "system": "http://unitsofmeasure.org",
      "code": "mmol/L"
    }
  }
}

Triển khai Resolvers cho Mutations

Dưới đây là cách triển khai resolvers cho mutations:

const mutationResolvers = {
  Mutation: {
    PatientCreate: async (_, { resource }, context) => {
      try {
        // Kiểm tra quyền
        if (!context.hasPermission('Patient', 'create')) {
          throw new Error('Unauthorized');
        }
        
        const client = context.getFHIRClient();
        const result = await client.create('Patient', resource);
        
        return {
          resource: result
        };
      } catch (error) {
        return {
          error: {
            issue: [
              {
                severity: 'error',
                code: 'processing',
                diagnostics: error.message
              }
            ]
          }
        };
      }
    },
    
    PatientUpdate: async (_, { id, resource }, context) => {
      try {
        // Kiểm tra quyền
        if (!context.hasPermission('Patient', 'update')) {
          throw new Error('Unauthorized');
        }
        
        const client = context.getFHIRClient();
        
        // Kiểm tra version để đảm bảo không bị conflict
        const existingPatient = await client.read('Patient', id);
        resource.id = id;
        
        if (existingPatient.meta && existingPatient.meta.versionId) {
          if (!resource.meta) resource.meta = {};
          resource.meta.versionId = existingPatient.meta.versionId;
        }
        
        const result = await client.update('Patient', id, resource);
        
        return {
          resource: result
        };
      } catch (error) {
        return {
          error: {
            issue: [
              {
                severity: 'error',
                code: 'processing',
                diagnostics: error.message
              }
            ]
          }
        };
      }
    },
    
    PatientDelete: async (_, { id }, context) => {
      try {
        // Kiểm tra quyền
        if (!context.hasPermission('Patient', 'delete')) {
          throw new Error('Unauthorized');
        }
        
        const client = context.getFHIRClient();
        
        // Đọc tài nguyên trước khi xóa để trả về
        const resource = await client.read('Patient', id);
        
        // Xóa tài nguyên
        await client.delete('Patient', id);
        
        return {
          resource
        };
      } catch (error) {
        return {
          error: {
            issue: [
              {
                severity: 'error',
                code: 'processing',
                diagnostics: error.message
              }
            ]
          }
        };
      }
    }
  }
};

Xử lý giao dịch (Transactions)

FHIR hỗ trợ giao dịch thông qua Bundle nhưng GraphQL không có khái niệm tương đương trực tiếp. Tuy nhiên, chúng ta có thể triển khai nó như một mutation:

input BundleEntryInput {
  resourceType: String!
  resource: ResourceInput!
  request: BundleEntryRequestInput!
}

input BundleEntryRequestInput {
  method: HTTPVerb!
  url: String!
}

enum HTTPVerb {
  GET
  PUT
  POST
  DELETE
  PATCH
}

input ResourceInput {
  # Union type cho tất cả resource inputs
  Patient: PatientInput
  Observation: ObservationInput
  Condition: ConditionInput
  MedicationRequest: MedicationRequestInput
  # ... các loại tài nguyên khác
}

type Mutation {
  # ... các mutations khác
  
  # Mutation cho transaction
  ProcessTransaction(entries: [BundleEntryInput!]!): TransactionResult
}

type TransactionResult {
  entries: [TransactionResultEntry]
  error: OperationOutcome
}

type TransactionResultEntry {
  resourceType: String
  resource: Resource
  response: TransactionEntryResponse
}

type TransactionEntryResponse {
  status: String
  location: String
  etag: String
  lastModified: Instant
}

## 5. Performance Considerations

Hiệu suất là một khía cạnh quan trọng khi triển khai GraphQL với FHIR, đặc biệt khi làm việc với dữ liệu y tế có thể rất lớn và phức tạp. Dưới đây là một số vấn đề về hiệu suất và cách giải quyết:

### Vấn đề N+1 Queries

Vấn đề N+1 là một trong những thách thức phổ biến nhất với GraphQL. Khi một truy vấn yêu cầu danh sách các đối tượng và thông tin chi tiết về mỗi đối tượng, server có thể thực hiện 1 truy vấn để lấy danh sách và sau đó N truy vấn bổ sung cho mỗi đối tượng.

**Ví dụ:**
```graphql
query {
  PatientList(_count: 100) {
    edges {
      resource {
        id
        name { ... }
        observations {  # Có thể gây ra 100 truy vấn riêng biệt!
          edges {
            resource { ... }
          }
        }
      }
    }
  }
}

Giải pháp: DataLoader

DataLoader là một thư viện giúp gom nhóm và cache các truy vấn:

const DataLoader = require('dataloader');

// Trong context của mỗi request
const createLoaders = (fhirClient) => {
  return {
    observationsByPatient: new DataLoader(async (patientIds) => {
      // Thay vì lấy từng bệnh nhân một, gộp thành một truy vấn
      const bundle = await fhirClient.search('Observation', {
        subject: patientIds.map(id => `Patient/${id}`).join(',')
      });
      
      // Tổ chức kết quả theo ID bệnh nhân
      const resultsByPatient = patientIds.map(id => {
        const entries = bundle.entry
          .filter(entry => {
            const subjectRef = entry.resource.subject?.reference;
            return subjectRef === `Patient/${id}`;
          })
          .map(entry => entry.resource);
          
        return {
          count: entries.length,
          edges: entries.map(resource => ({ resource }))
        };
      });
      
      return resultsByPatient;
    }),
    
    // Các loaders khác...
  };
};

// Sử dụng trong resolver
const resolvers = {
  Patient: {
    observations: async (patient, args, context) => {
      return context.loaders.observationsByPatient.load(patient.id);
    }
  }
};

Giới hạn độ phức tạp của truy vấn

Truy vấn GraphQL có thể trở nên cực kỳ phức tạp, đặc biệt với hệ thống FHIR có nhiều tài nguyên liên kết với nhau. Điều này có thể dẫn đến quá tải server.

Giải pháp: Phân tích và giới hạn truy vấn

const { createComplexityLimitRule } = require('graphql-validation-complexity');

const server = new ApolloServer({
  schema: fhirSchema,
  validationRules: [
    createComplexityLimitRule(1000, {
      scalarCost: 1,
      objectCost: 2,
      listFactor: 10
    })
  ],
  context: ({ req }) => ({
    // ...
  })
});

Phân trang hiệu quả

FHIR có thể chứa lượng dữ liệu lớn, và việc tải tất cả cùng một lúc là không hiệu quả.

Giải pháp: Cursor-based Pagination

type PatientConnection {
  edges: [PatientEdge]
  pageInfo: PageInfo
}

type PatientEdge {
  cursor: String
  resource: Patient
}

type PageInfo {
  hasNextPage: Boolean
  hasPreviousPage: Boolean
  startCursor: String
  endCursor: String
}

type Query {
  PatientList(first: Int, after: String): PatientConnection
}

Triển khai resolver:

const resolvers = {
  Query: {
    PatientList: async (_, { first = 10, after }, context) => {
      const cursor = after ? decodeCursor(after) : null;
      
      // Sử dụng _count và _getpagesoffset của FHIR
      const bundle = await context.fhirClient.search('Patient', {
        _count: first,
        _getpagesoffset: cursor ? cursor.offset + 1 : 0
      });
      
      const edges = bundle.entry.map((entry, index) => ({
        cursor: encodeCursor({ offset: cursor ? cursor.offset + index + 1 : index }),
        resource: entry.resource
      }));
      
      return {
        edges,
        pageInfo: {
          hasNextPage: bundle.link.some(link => link.relation === 'next'),
          hasPreviousPage: bundle.link.some(link => link.relation === 'previous'),
          startCursor: edges.length > 0 ? edges[0].cursor : null,
          endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null
        }
      };
    }
  }
};

Caching

Caching là cách hiệu quả để tăng hiệu suất, đặc biệt với dữ liệu ít thay đổi.

Giải pháp: Response Caching và Resource Caching

const { ApolloServer } = require('apollo-server-express');
const responseCachePlugin = require('apollo-server-plugin-response-cache');
const RedisCache = require('apollo-server-cache-redis').RedisCache;

const server = new ApolloServer({
  schema: fhirSchema,
  cache: new RedisCache({
    host: 'redis-server',
    port: 6379
  }),
  plugins: [responseCachePlugin({
    // Các truy vấn cơ bản như Patient, Observation có thể được cache
    // nhưng các truy vấn tìm kiếm không nên cache
    shouldReadFromCache: ({ request }) => {
      const query = request.query.toLowerCase();
      return !query.includes('list') && !query.includes('search');
    },
    // TTL cho cache
    ttl: 60 // 60 seconds
  })],
  context: ({ req }) => ({
    // ...
  })
});

Chọn lọc trường dữ liệu

Một trong những lợi ích của GraphQL là khả năng chọn chính xác các trường cần thiết, giảm lượng dữ liệu truyền tải. Tuy nhiên, điều này cần được triển khai đúng cách ở phía server.

Giải pháp: Tối ưu hóa FHIR _elements

const { getFieldsFromInfo } = require('graphql-fields-list');

const resolvers = {
  Query: {
    Patient: async (_, { id }, context, info) => {
      // Trích xuất danh sách trường được yêu cầu
      const fields = getFieldsFromInfo(info);
      
      // Chuyển đổi thành tham số _elements của FHIR
      const elements = convertGraphQLFieldsToFHIRElements(fields);
      
      // Thêm vào truy vấn FHIR
      const params = elements.length > 0 ? { _elements: elements.join(',') } : {};
      
      return context.fhirClient.read('Patient', id, params);
    }
  }
};

// Hàm chuyển đổi trường GraphQL sang FHIR elements
function convertGraphQLFieldsToFHIRElements(fields) {
  const elements = [];
  
  // Luôn lấy id và resourceType
  elements.push('id', 'resourceType');
  
  // Ánh xạ các trường GraphQL sang paths FHIR
  Object.keys(fields).forEach(field => {
    if (field === 'id' || field === 'resourceType') return;
    
    if (typeof fields[field] === 'object') {
      // Xử lý trường nested
      const nestedFields = Object.keys(fields[field]);
      if (nestedFields.length > 0) {
        elements.push(field);
      }
    } else {
      elements.push(field);
    }
  });
  
  return elements;
}

Xử lý tải cao

Đối với các hệ thống y tế lớn, khối lượng truy vấn có thể rất cao.

Giải pháp: Horizontal Scaling và Rate Limiting

const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');

// Rate limiting middleware
const limiter = rateLimit({
  store: new RedisStore({
    client: redis.createClient({
      host: 'redis-server',
      port: 6379
    })
  }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests, please try again later'
});

// Áp dụng cho endpoint GraphQL
app.use('/graphql', limiter);

Monitoring và Logging

Để tối ưu hiệu suất, việc giám sát và ghi log là cần thiết.

Giải pháp: Apollo Studio và Custom Logging

const server = new ApolloServer({
  schema: fhirSchema,
  plugins: [
    {
      async serverWillStart() {
        console.log('Server starting up!');
      },
      async requestDidStart(ctx) {
        console.log('Request started:', ctx.request.query);
        
        return {
          async parsingDidStart(ctx) {
            console.log('Parsing started!');
          },
          async validationDidStart(ctx) {
            console.log('Validation started!');
          },
          async executionDidStart(ctx) {
            console.log('Execution started!');
            const start = Date.now();
            
            return async () => {
              const duration = Date.now() - start;
              console.log(`Execution completed in ${duration}ms`);
            };
          },
          async didEncounterErrors(ctx) {
            console.error('Encountered errors:', ctx.errors);
          },
          async willSendResponse(ctx) {
            console.log('Will send response:', ctx.response);
          }
        };
      }
    }
  ],
  context: ({ req }) => ({
    // ...
  })
});

Kết luận

GraphQL với FHIR R5 mang lại một cách tiếp cận mạnh mẽ và linh hoạt cho việc truy vấn và thao tác dữ liệu y tế. Với khả năng lấy chính xác dữ liệu cần thiết trong một request duy nhất, GraphQL giải quyết nhiều vấn đề mà REST API truyền thống gặp phải khi làm việc với dữ liệu FHIR phức tạp.

Trong bài viết này, chúng ta đã khám phá:

  • Cách xây dựng GraphQL Schema cho FHIR R5

  • Triển khai GraphQL Server với đầy đủ tính năng xác thực và phân quyền

  • Thực hiện các truy vấn phức tạp để lấy dữ liệu liên quan từ nhiều tài nguyên

  • Thực hiện mutations để tạo, cập nhật và xóa dữ liệu

  • Các chiến lược tối ưu hiệu suất để đảm bảo server GraphQL FHIR hoạt động hiệu quả

Tuy nhiên, việc triển khai GraphQL cho FHIR không phải không có thách thức. Các vấn đề như N+1 queries, quản lý độ phức tạp của truy vấn và caching cần được xem xét cẩn thận. Bằng cách áp dụng các kỹ thuật và công cụ được đề xuất trong bài viết này, bạn có thể xây dựng một API GraphQL FHIR mạnh mẽ, linh hoạt và hiệu quả.

Trong các bài viết tiếp theo, chúng ta sẽ khám phá các chủ đề nâng cao hơn như FHIR Subscriptions qua GraphQL, tích hợp GraphQL với SMART on FHIR, và các chiến lược bảo mật nâng cao. Hãy tiếp tục theo dõi chuỗi bài viết về FHIR của chúng tôi!

Tài nguyên tham khảo

PreviousFHIR Documents In R5NextFHIR Security trong R5

Last updated 2 months ago

HL7 FHIR GraphQL Specification:

Apollo Server Documentation:

DataLoader:

SMART on FHIR:

GraphQL-FHIR:

HAPI FHIR:

https://hl7.org/fhir/graphql.html
https://www.apollographql.com/docs/apollo-server/
https://github.com/graphql/dataloader
https://docs.smarthealthit.org/
https://github.com/Asymmetrik/graphql-fhir
https://hapifhir.io/