The Codest
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Branże
    • Fintech i bankowość
    • E-commerce
    • Adtech
    • Healthtech
    • Produkcja
    • Logistyka
    • Motoryzacja
    • IOT
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
  • O nas
  • Nasze Usługi
    • Software Development
      • Frontend Development
      • Backend Development
    • Zespoły IT
      • Programiści frontendowi
      • Backend Dev
      • Inżynierowie danych
      • Inżynierowie rozwiązań chmurowych
      • Inżynierowie QA
      • Inne
    • Konsultacje IT
      • Audyt i doradztwo
  • Wartość dla
    • CEO
    • CTO
    • Delivery Managera
  • Nasz zespół
  • Case Studies
  • Nasze Know How
    • Blog
    • Meetups
    • Webinary
    • Raporty
Kariera Skontaktuj się z nami
Strzałka w tył WSTECZ
2022-01-13
Software Development

Polimorfizm w Ruby i GraphQL

Łukasz Brzeszcz

W tym artykule przedstawię zastosowanie polimorfizmu w GraphQL. Zanim jednak zacznę, warto przypomnieć czym są polimorfizm i GraphQL.

Polimorfizm

Polimorfizm jest kluczowym składnikiem programowanie obiektowe. Upraszczając, opiera się ona na fakcie, że obiekty różnych klas mają dostęp do tego samego interfejsu, co oznacza, że od każdego z nich możemy oczekiwać tej samej funkcjonalności, ale niekoniecznie zaimplementowanej w ten sam sposób. W Programiści Ruby może uzyskać polimorfizm na trzy sposoby:

Dziedziczenie

Dziedziczenie polega na utworzeniu klasy nadrzędnej i klas podrzędnych (tj. dziedziczących z klasy nadrzędnej). Podklasy otrzymują funkcjonalność klasy nadrzędnej, a także umożliwiają zmianę i dodanie funkcjonalności.

Przykład:

class Document
  attr_reader :name
koniec

class PDFDocument < Dokument
  def extension
    pdf
  end
end

class ODTDocument < Document
  def extension
    odt
  end
end

Moduły

Moduły w Ruby mają wiele zastosowań. Jednym z nich są mixiny (przeczytaj więcej o mixinach w Ostateczny podział: Ruby vs. Python). Mixiny w Rubim mogą być używane podobnie do interfejsów w innych językach programowania (np. w Java) można na przykład zdefiniować w nich metody wspólne dla obiektów, które będą zawierały dany mixin. Dobrą praktyką jest umieszczanie w modułach metod tylko do odczytu, czyli takich, które nie będą modyfikować stanu tego obiektu.

Przykład:

moduł Taxable
  def tax

     cena * 0.23
  koniec
koniec

class Samochód
  include Taxable
 attr_reader :price
end

class Book
  include Taxable

 attr_reader :price
koniec

Pisanie na klawiaturze

Jest to jedna z kluczowych cech języków dynamicznie typowanych. Nazwa pochodzi od słynnego testu: jeśli wygląda jak kaczka, pływa jak kaczka i kwacze jak kaczka, to prawdopodobnie jest kaczką. Test programista nie musi interesować się, do jakiej klasy należy dany obiekt. Ważne są metody, które można wywołać na tym obiekcie.

Korzystając z klas zdefiniowanych w powyższym przykładzie:

class Samochód
  attr_reader :price

 def initialize(price)
    @price = price
   end
end

class Book
  attr_reader :price

 def initialize(price)
    @price = price
  end
end

car = Car.new(20.0)
book = Book.new(10.0)

[car, book].map(&:price

GrapQL

GraphQL to stosunkowo nowy język zapytań dla interfejsów API. Jego zalety obejmują fakt, że ma bardzo prostą składnię, a ponadto klient decyduje, co dokładnie chce uzyskać, ponieważ każdy klient otrzymuje dokładnie to, czego chce i nic więcej.

Przykładowe zapytanie w GraphQL:

{
  allUsers {
     users {
        id
        login
        email

       }
     }
   }

Przykładowa odpowiedź:

{
  "allUsers": {
    "users": [
     {
        "id": 1,
        "login": "user1",
        "email": "[email protected]"
      },
      {
        "id": 2,
        "login": "user2",
        "email": "[email protected]"
      },
    ]
  }
}

To chyba wszystko, co powinniśmy wiedzieć w tym momencie. Przejdźmy więc do rzeczy.

Przedstawienie problemu

Aby jak najlepiej zrozumieć problem i jego rozwiązanie, stwórzmy przykład. Dobrze by było, gdyby przykład był zarówno oryginalny, jak i dość przyziemny. Taki, z którym każdy z nas może się kiedyś zetknąć. Może... zwierzęta? Tak! Świetny pomysł!

polimorfizm ruby i grapql - zwierzęta

Załóżmy, że mamy aplikację backendową napisaną w języku Ruby on Rails. Jest on już przystosowany do obsługi powyższego schematu. Załóżmy również, że mamy już GraphQL skonfigurowany. Chcemy umożliwić klientowi złożenie zapytania w ramach następującej struktury:

{
 allZoos : {
    zoo: {
      nazwa
      miasto
      zwierzęta: {
        ...
      }
    }
  }
}

Co należy wstawić zamiast trzech kropek, aby uzyskać brakujące informacje - dowiemy się później.

Wdrożenie

Poniżej przedstawię kroki potrzebne do osiągnięcia tego celu.

Dodawanie zapytania do QueryType

Najpierw należy zdefiniować, co dokładnie oznacza zapytanie allZoos. Aby to zrobić, musimy odwiedzić plikapp/graphql/types/query_type.rb i zdefiniuj zapytanie:

   moduł Typy
      class QueryType < Types::BaseObject
       field :all_zoos, [Types::ZooType], null: false

       def all_zoos
          Zoo.all
       end
    end
 end

Zapytanie jest już zdefiniowane. Teraz nadszedł czas, aby zdefiniować typy zwracane.

Definicja typów

Pierwszym wymaganym typem będzie ZooType. Zdefiniujmy go w pliku app/graphql/types/ zoo_type.rb:

moduł Typy
  class ZooType < Types::BaseObject
    pole :name, String, null: false
    field :city, String, null: false
    field :animals, [Types::AnimalType], null: false
  end
end

Teraz nadszedł czas na zdefiniowanie typu AnimalType:

moduł Typy
  class AnimalType < Types::BaseUnion
   possible_types ElephantType, CatType, DogType

     def self.resolve_type(obj, ctx)
       if obj.is_a?(Elephant)
          ElephantType
       elsif obj.is_a?(Cat)
         CatType
       elsif obj.is_a?(Dog)
        DogType
      end
    end
  end
end

Co widzimy w kod powyżej?

  1. AnimalType dziedziczy po Types::BaseUnion.
  2. Musimy wymienić wszystkie typy, które mogą tworzyć dany związek.
    3.zastępujemy funkcję self.resolve_object(obj, ctx),która musi zwracać typ danego obiektu.

Następnym krokiem jest zdefiniowanie typów zwierząt. Wiemy jednak, że niektóre pola są wspólne dla wszystkich zwierząt. Uwzględnijmy je w typie AnimalInterface:

moduł Typy
  moduł AnimalInterface
    include Types::BaseInterface

    field :name, String, null: false
    field :age, Integer, null: false
  end
end

Mając ten interfejs, możemy przystąpić do definiowania typów konkretnych zwierząt:

moduł Typy
  class ElephantType < Types::BaseObject
    implementuje Types::AnimalInterface

    field :trunk_length, Float, null: false
  end
end

moduł Typy
  class CatType < Types::BaseObject
   implementuje Types::AnimalInterface

   pole :hair_type, String, null: false
  end
end

moduł Typy
  class DogType < Types::BaseObject
    implementuje Types::AnimalInterface

     field :breed, String, null: false
  end
end

To jest to! Gotowe! Ostatnie pytanie: jak możemy wykorzystać to, co zrobiliśmy od strony klienta?

Tworzenie zapytania

{
 allZoos : {
   zoo: {
      nazwa
      miasto
      zwierzęta: {
        __typename

        ... on ElephantType {
          nazwa
          wiek
          trunkLength
        }

         ... on CatType {
          nazwa
          wiek
          hairType
         }
         ... on DogType {
          nazwa
          wiek
          rasa
         }
       }
     }
   }
 }

Możemy tutaj użyć dodatkowego pola __typename, które zwróci dokładny typ danego elementu (np. CatType). Jak będzie wyglądać przykładowa odpowiedź?

{
  "allZoos": [

   {
      "name": "Natura Artis Magistra",
      "city": "Amsterdam",
      "zwierzęta": [
        {
          "__typename": "ElephantType"
          "name": "Franco",
          "age": 28,
          "trunkLength": 9.27
         },
         {
         "__typename": "DogType"
         "name": "Jack",
         "age": 9,
         "breed": "Jack Russell Terrier"
        },
      ]
    }
  ]
} 

Analiza

Widoczna jest jedna wada tego podejścia. W zapytaniu musimy wpisać nazwę i wiek w każdym typie, mimo że wiemy, że wszystkie zwierzęta mają te pola. Nie jest to uciążliwe, gdy kolekcja zawiera zupełnie różne obiekty. W tym przypadku jednak zwierzęta mają prawie wszystkie pola wspólne. Czy można to jakoś poprawić?

Oczywiście! Dokonujemy pierwszej zmiany w pliku app/graphql/types/zoo_type.rb:

moduł Typy
  class ZooType < Types::BaseObject
    pole :name, String, null: false
    field :city, String, null: false
    field :animals, [Types::AnimalInterface], null: false
  end
end

Nie potrzebujemy już związku, który zdefiniowaliśmy wcześniej. Zmieniamy się Types::AnimalType do Types::AnimalInterface.

Następnym krokiem jest dodanie funkcji zwracającej typ z pliku Typy :: AnimalInterface a także dodać listę orphan_types, czyli typów, które nigdy nie są bezpośrednio używane:

moduł Typy
  moduł AnimalInterface
    include Types::BaseInterface

   field :name, String, null: false
   field :age, Integer, null: false

   definition_methods do
      def resolve_type(obj, ctx)
        if obj.is_a?(Elephant)
          ElephantType
        elsif obj.is_a?(Cat)
          CatType
        elsif obj.is_a?(Dog)
          DogType
        end
      end
    end
    orphan_types Types::ElephantType, Types::CatType, Types::DogType
  end
end

Dzięki temu prostemu zabiegowi zapytanie ma mniej skomplikowaną formę:

{
  allZoos : {
   zoo: {
      nazwa
      miasto
      zwierzęta: {
        __typename
        nazwa
        wiek

       ... on ElephantType {
          trunkLength

       }
       ... on CatType {
          hairType

       }
       ... on DogType {
          rasa

        }
      }
    }
  }
}

Podsumowanie

GraphQL to naprawdę świetne rozwiązanie. Jeśli jeszcze go nie znasz, spróbuj. Zaufaj mi, warto. Świetnie radzi sobie z rozwiązywaniem problemów pojawiających się np. w REST API. Jak pokazałem powyżej, polimorfizm nie jest dla niego prawdziwą przeszkodą. Przedstawiłem dwie metody radzenia sobie z tym problemem.
Przypomnienie:

  • Jeśli operujesz na liście obiektów o wspólnej bazie lub wspólnym interfejsie - użyj interfejsów,
  • Jeśli operujesz na liście obiektów o innej strukturze, użyj innego interfejsu - użyj unii

Czytaj więcej

GraphQL Ruby. Co z wydajnością?

Szyny i inne środki transportu

Rails Development z TMUX, Vim, Fzf + Ripgrep

Powiązane artykuły

Abstrakcyjna ilustracja malejącego wykresu słupkowego z rosnącą strzałką i złotą monetą symbolizującą efektywność kosztową lub oszczędności. Logo The Codest pojawia się w lewym górnym rogu wraz ze sloganem "In Code We Trust" na jasnoszarym tle.
Software Development

Jak skalować zespół programistów bez utraty jakości produktu?

Skalujesz swój zespół programistów? Dowiedz się, jak się rozwijać bez poświęcania jakości produktu. W tym przewodniku omówiono oznaki, że nadszedł czas na skalowanie, strukturę zespołu, zatrudnianie, przywództwo i narzędzia - a także sposób, w jaki The Codest może...

THEECODEST
Software Development

Tworzenie przyszłościowych aplikacji internetowych: spostrzeżenia zespołu ekspertów The Codest

Odkryj, w jaki sposób The Codest wyróżnia się w tworzeniu skalowalnych, interaktywnych aplikacji internetowych przy użyciu najnowocześniejszych technologii, zapewniając płynne doświadczenia użytkowników na wszystkich platformach. Dowiedz się, w jaki sposób nasza wiedza napędza transformację cyfrową i biznes...

THEECODEST
Software Development

10 najlepszych firm tworzących oprogramowanie na Łotwie

Dowiedz się więcej o najlepszych łotewskich firmach programistycznych i ich innowacyjnych rozwiązaniach w naszym najnowszym artykule. Odkryj, w jaki sposób ci liderzy technologiczni mogą pomóc w rozwoju Twojej firmy.

thecodest
Rozwiązania dla przedsiębiorstw i scaleupów

Podstawy tworzenia oprogramowania Java: Przewodnik po skutecznym outsourcingu

Zapoznaj się z tym niezbędnym przewodnikiem na temat skutecznego tworzenia oprogramowania Java outsourcing, aby zwiększyć wydajność, uzyskać dostęp do wiedzy specjalistycznej i osiągnąć sukces projektu z The Codest.

thecodest
Software Development

Kompletny przewodnik po outsourcingu w Polsce

Wzrost liczby outsourcing w Polsce jest napędzany przez postęp gospodarczy, edukacyjny i technologiczny, sprzyjający rozwojowi IT i przyjazny klimat dla biznesu.

TheCodest

Subskrybuj naszą bazę wiedzy i bądź na bieżąco!

    O nas

    The Codest - Międzynarodowa firma programistyczna z centrami technologicznymi w Polsce.

    Wielka Brytania - siedziba główna

    • Office 303B, 182-184 High Street North E6 2JA
      Londyn, Anglia

    Polska - lokalne centra technologiczne

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polska

      The Codest

    • Strona główna
    • O nas
    • Nasze Usługi
    • Case Studies
    • Nasze Know How
    • Kariera
    • Słownik

      Nasze Usługi

    • Konsultacje IT
    • Software Development
    • Backend Development
    • Frontend Development
    • Zespoły IT
    • Backend Dev
    • Inżynierowie rozwiązań chmurowych
    • Inżynierowie danych
    • Inne
    • Inżynierowie QA

      Raporty

    • Fakty i mity na temat współpracy z zewnętrznym partnerem programistycznym
    • Z USA do Europy: Dlaczego amerykańskie startupy decydują się na relokację do Europy?
    • Porównanie centrów rozwoju Tech Offshore: Tech Offshore Europa (Polska), ASEAN (Filipiny), Eurazja (Turcja)
    • Jakie są największe wyzwania CTO i CIO?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Warunki korzystania z witryny

    Copyright © 2025 by The Codest. Wszelkie prawa zastrzeżone.

    pl_PLPolish
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek pl_PLPolish