window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster υπάρχει ήδη') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() GraphQL Ruby. Τι γίνεται με τις επιδόσεις; - The Codest
The Codest
  • Σχετικά με εμάς
  • Υπηρεσίες
    • Ανάπτυξη λογισμικού
      • Ανάπτυξη Frontend
      • Backend Ανάπτυξη
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Μηχανικοί δεδομένων
      • Μηχανικοί cloud
      • Μηχανικοί QA
      • Άλλα
    • Συμβουλευτική
      • Έλεγχος & Συμβουλευτική
  • Βιομηχανίες
    • Fintech & Τραπεζική
    • E-commerce
    • Adtech
    • Healthtech
    • Κατασκευή
    • Εφοδιαστική
    • Αυτοκίνητο
    • IOT
  • Αξία για
    • CEO
    • CTO
    • Διευθυντής παράδοσης
  • Η ομάδα μας
  • Case Studies
  • Μάθετε πώς
    • Blog
    • Συναντήσεις
    • Διαδικτυακά σεμινάρια
    • Πόροι
Καριέρα Ελάτε σε επαφή
  • Σχετικά με εμάς
  • Υπηρεσίες
    • Ανάπτυξη λογισμικού
      • Ανάπτυξη Frontend
      • Backend Ανάπτυξη
    • Staff Augmentation
      • Frontend Developers
      • Backend Developers
      • Μηχανικοί δεδομένων
      • Μηχανικοί cloud
      • Μηχανικοί QA
      • Άλλα
    • Συμβουλευτική
      • Έλεγχος & Συμβουλευτική
  • Αξία για
    • CEO
    • CTO
    • Διευθυντής παράδοσης
  • Η ομάδα μας
  • Case Studies
  • Μάθετε πώς
    • Blog
    • Συναντήσεις
    • Διαδικτυακά σεμινάρια
    • Πόροι
Καριέρα Ελάτε σε επαφή
Πίσω βέλος GO BACK
2021-06-30
Ανάπτυξη λογισμικού

GraphQL Ruby. Τι γίνεται με τις επιδόσεις;

The Codest

Tomasz Szkaradek

Αρχιτέκτονας ανάπτυξης

Η GraphQL, όπως κάθε τεχνολογία, έχει τα προβλήματά της, ορισμένα από αυτά προκύπτουν άμεσα από την αρχιτεκτονική και ορισμένα είναι πανομοιότυπα με αυτά που βλέπουμε σε οποιαδήποτε άλλη εφαρμογή. Ωστόσο, οι λύσεις είναι εντελώς διαφορετικές.

Για να παρουσιάσουμε το πρόβλημα, ας υποθέσουμε την ακόλουθη αρχιτεκτονική εφαρμογής:

https://drive.google.com/file/d/1N4sWPJSls0S8FFHbpHCUVHBNBpEuSsyz/view

Και εδώ το αντίστοιχο ερώτημα στο GraphQL για να κατεβάσετε τα δεδομένα. Λαμβάνουμε όλους τους συνδέσμους, μαζί με την αφίσα και τους συνδέσμους της που προστίθενται στο σύστημα,

{
  allLinks {
    id
    url
    description
    createdAt
    postedBy {
      id
      name
      links {
        id
      }
    }
  }
}

Όπως φαίνεται παρακάτω, μπορούμε να δούμε το κλασικό πρόβλημα n+1 με τις σχέσεις εδώ.

Φόρτωση συνδέσμων (0.4ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση χρηστών (0.3ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 40], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση συνδέσμων (0.3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση χρηστών (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 38], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση συνδέσμων (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 38]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση χρήστη (0.2ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 36], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση συνδέσμων (0.1ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 36]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση χρήστη (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 34], ["LIMIT", 1]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση συνδέσμων (0.2ms) SELECT "links".* FROM "links" WHERE "links". "user_id" = ?  [["user_id", 34]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση χρήστη (0.1ms) SELECT "users".* FROM "users" WHERE "users". "id" = ? LIMIT ?  [["id", 32], ["LIMIT", 1]]

Σε αυτή την περίπτωση, λειτουργεί ακριβώς όπως αυτό το κομμάτι του κωδικός:
Link.all.map(&:user).map(&:links).

Φαίνεται ότι γνωρίζουμε τη λύση του προβλήματος: Link.includes(user: :links).map(&:user).map(&:links), αλλά θα λειτουργήσει πραγματικά; Ας το ελέγξουμε!

Για να επαληθεύσω τη διόρθωση, άλλαξα το GraphQL ερώτημα για να χρησιμοποιήσετε μόνο μερικά πεδία και καμία σχέση.

{
  allLinks {
    id
    url
    description
    createdAt
  }
}

Δυστυχώς, το αποτέλεσμα δείχνει ότι, παρά την έλλειψη συνδέσμων σε σχέση με τον χρήστη και τους συνδέσμους του, εξακολουθούμε να επισυνάπτουμε αυτά τα δεδομένα στο ερώτημα της βάσης δεδομένων. Δυστυχώς, είναι περιττά και, με μια ακόμη πιο περίπλοκη δομή, αποδεικνύεται απλώς αναποτελεσματικό.

Επεξεργασία από GraphqlController#execute ως */*
  Παράμετροι: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}", "graphql"=>{{"query"=>"{n allLinks {n idn urln descriptionn createdAtn }n}"}}
  Φόρτωση συνδέσμων (0.3ms) SELECT "links".* FROM "links" ORDER BY created_at DESC
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση χρήστη (0.3ms) SELECT "users".* FROM "users" WHERE "users". "id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 40], ["id", 38], ["id", 36], ["id", 34], ["id", 32], ["id", 30], ["id", 28], ["id", 26], ["id", 24], ["id", 22], ["id", 20], ["id", 18], ["id", 16], ["id", 14], ["id", 12], ["id", 10], ["id", 8], ["id", 6], ["id", 4], ["id", 2]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση συνδέσμων (0.3ms) SELECT "links".* FROM "links" WHERE "links". "user_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["user_id", 2], ["user_id", 4], ["user_id", 6], ["user_id", 8], ["user_id", 10], ["user_id", 12], ["user_id", 14], ["user_id", 16], ["user_id", 18], ["user_id", 20], ["user_id", 22], ["user_id", 24], ["user_id", 26], ["user_id", 28], ["user_id", 30], ["user_id", 32], ["user_id", 34], ["user_id", 36], ["user_id", 38], ["user_id", 40]]
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
Ολοκληρώθηκε 200 OK σε 39ms (Views: 0.7ms | ActiveRecord: 0.9ms | Allocations: 8730)

Στο GraphQL, τέτοια προβλήματα επιλύονται διαφορετικά,απλά με τη φόρτωση δεδομένων σε παρτίδες, υποθέτοντας ότι τα δεδομένα είναι απαραίτητα όταν μπαίνουν στο ερώτημα. Πρόκειται για μια τέτοια τεμπέλικη φόρτωση. Μια από τις πιο δημοφιλείς βιβλιοθήκες είναι η https://github.com/Shopify/graphql-batch/.

Δυστυχώς, η εγκατάστασή του δεν είναι τόσο εύκολη όσο φαίνεται. Οι φορτωτές δεδομένων είναι διαθέσιμοι εδώ: https://github.com/Shopify/graphql-batch/tree/master/examples, εννοώ το RecordLoader και η κλάσηAssociationLoader κατηγορία. Ας εγκαταστήσουμε κλασικά την gem 'graphql-batch' βιβλιοθήκη και στη συνέχεια να την προσθέσουμε στο σχήμα μας, καθώς και στους φορτωτές:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  ...
end

Και οι τύποι μας:

# graphql-ruby/app/graphql/types/link_type.rb
module Τύποι
  class LinkType < BaseNode
    πεδίο :created_at, DateTimeType, null: false
    πεδίο :url, String, null: false
    field :description, String, null: false
    field :posted_by, UserType, null: false, method: :user
    field :votes, [Types::VoteType], null: false

    def user
      Loaders::RecordLoader.for(User).load(object.user_id)
    end
  end
end

# graphql-ruby/app/graphql/types/user_type.rb
module Types
  class UserType < BaseNode
    field :created_at, DateTimeType, null: false
    πεδίο :name, String, null: false
    field :email, String, null: false
    field :votes, [VoteType], null: false
    field :links, [LinkType], null: false

    def links
      Loaders::AssociationLoader.for(User, :links).load(object)
    end
  end
end

Ως αποτέλεσμα της χρήσης των φορτωτών, ομαδοποιούμε τα δεδομένα και αναζητούμε δεδομένα σε δύο απλά ερωτήματα sql:

Ξεκίνησε POST "/graphql" για ::1 στις 2021-06-16 22:40:17 +0200
   (0.1ms) SELECT sqlite_version(*)
Επεξεργασία από GraphqlController#execute ως */*
  Παράμετροι: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n }n}"}}
  Φόρτωση συνδέσμων (0.4ms) SELECT "links".* FROM "links"
  ↳ app/controllers/graphql_controller.rb:5:in `execute'
  Φόρτωση χρηστών (0.9ms) SELECT "users".* FROM "users" WHERE "users". "id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["id", 2], ["id", 4], ["id", 6], ["id", 8], ["id", 10], ["id", 12], ["id", 14], ["id", 16], ["id", 18], ["id", 20], ["id", 22], ["id", 24], ["id", 26], ["id", 28], ["id", 30], ["id", 32], ["id", 34], ["id", 36], ["id", 38], ["id", 40]]
  ↳ app/graphql/loaders/record_loader.rb:12:in `perform'
  Φόρτωση συνδέσμων (0.5ms) SELECT "links".* FROM "links" WHERE "links". "user_id" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) [["user_id", 2], ["user_id", 4], ["user_id", 6], ["user_id", 8], ["user_id", 10], ["user_id", 12], ["user_id", 14], ["user_id", 16], ["user_id", 18], ["user_id", 20], ["user_id", 22], ["user_id", 24], ["user_id", 26], ["user_id", 28], ["user_id", 30], ["user_id", 32], ["user_id", 34], ["user_id", 36], ["user_id", 38], ["user_id", 40]]
  ↳ app/graphql/loaders/association_loader.rb:46:in `preload_association'
Ολοκληρώθηκε 200 OK σε 62ms (Views: 1.3ms | ActiveRecord: 1.8ms | Allocations: 39887)

Υπάρχουν και άλλες λύσεις που επιλύουν αυτό το πρόβλημα, όπως:

https://github.com/exAspArk/batch-loader#basic-example

Πολυπλοκότητα των ερωτημάτων

Τα ερωτήματα N + 1 δεν είναι τα πάντα, στο GraphQL μπορούμε να μεταφέρουμε ελεύθερα τα επόμενα χαρακτηριστικά. Από προεπιλογή, έχει οριστεί σε 1. Αυτό μπορεί μερικές φορές να είναι υπερβολικό για τον διακομιστή, ειδικά σε μια κατάσταση όπου μπορούμε να φωλιάζουμε ελεύθερα δεδομένα. Πώς να το αντιμετωπίσετε; Μπορούμε να περιορίσουμε την πολυπλοκότητα του ερωτήματος, αλλά για να το κάνουμε αυτό, πρέπει επίσης να καθορίσουμε το κόστος τους στα χαρακτηριστικά. Από προεπιλογή έχει οριστεί σε 1. Ορίζουμε αυτό το κόστος χρησιμοποιώντας την εντολή πολυπλοκότητα: χαρακτηριστικό, όπου μπορούμε να εισάγουμε δεδομένα: field: links, [LinkType], null: false, complexity: 101. Αν ο περιορισμός πρόκειται να λειτουργήσει πραγματικά, θα πρέπει ακόμα να εισαγάγετε το μέγιστο όριο στο σύστημά σας:

class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  max_complexity 100
  ...
end

Εντοπισμός

GraphQL επεξεργάζεται τα ερωτήματα με διαφορετικό τρόπο και η ανίχνευση δεν είναι τόσο απλή αν συγκριθεί με αυτό που μπορούμε να κάνουμε τοπικά. Δυστυχώς, το rack mini profiler ή ένα κανονικό αρχείο καταγραφής SQL δεν θα μας πει τα πάντα και δεν θα μας υποδείξει ποιο μέρος του ερωτήματος είναι υπεύθυνο για μια συγκεκριμένη χρονική φέτα. Στην περίπτωση της GraphQL-Ruby, μπορούμε να χρησιμοποιήσουμε εμπορικές λύσεις που είναι διαθέσιμες εδώ: https://graphql-ruby.org/queries/tracing, ή να προσπαθήσουμε να προετοιμάσουμε τη δική μας ανίχνευση. Παρακάτω, το απόσπασμα μοιάζει με έναν τοπικό ανιχνευτή.

# lib/my_custom_tracer.rb
class MyCustomTracer  'graphql.lex',
    'parse' => 'graphql.parse',
    'validate' => 'graphql.validate',
    'analyze_query' => 'graphql.analyze_query',
    'analyze_multiplex' => 'graphql.analyze_multiplex',
    'execute_multiplex' => 'graphql.execute_multiplex',
    'execute_query' => 'graphql.execute_query',
    'execute_query_lazy' => 'graphql.execute_query_lazy'
  }

  def platform_trace(platform_key, key, _data, &block)
    start = ::Process.clock_gettime ::Process::CLOCK_MONOTONIC
    result = block.call
    duration = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start
    observe(platform_key, key, duration)
    αποτέλεσμα
  τέλος

  def platform_field_key(type, field)
    "graphql.#{type.graphql_name}.#{field.graphql_name}"
  end

  def platform_authorized_key(type)
    "graphql.authorized.#{type.graphql_name}"
  end

  def platform_resolve_type_key(type)
    "graphql.resolve_type.#{type.graphql_name}"
  end

  def observe(platform_key, key, duration)
    return if key == 'authorized'

    puts "platform_key: #{platform_key}, key: #{key}, duration: #{(duration * 1000).round(5)} ms".yellow
  end
end

Η εγκατάσταση είναι επίσης εξαιρετικά απλή, θα πρέπει να συμπεριλάβετε τις πληροφορίες του ανιχνευτή στο σχήμα tracer (MyCustomTracer.new) διαμόρφωση. Όπως στο παρακάτω παράδειγμα:

# graphql-ruby/app/graphql/graphql_tutorial_schema.rb
class GraphqlTutorialSchema < GraphQL::Schema
  query Types::QueryType
  mutation Types::MutationType
  use GraphQL::Batch
  tracer(MyCustomTracer.new)
  ...
end

Η έξοδος από μια τέτοια ανίχνευση μοιάζει ως εξής:

Ξεκίνησε POST "/graphql" για ::1 στις 2021-06-17 22:02:44 +0200
   (0.1ms) SELECT sqlite_version(*)
Επεξεργασία από GraphqlController#execute ως */*
  Παράμετροι: {"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n }n}", "graphql"=>{"query"=>"{n allLinks {n idn urln descriptionn createdAtn postedBy {n idn namen links {n idn }n }n }n }n}"}}
platform_key: graphql.lex, key: lex, duration: 0.156 ms
platform_key: graphql.parse, key: parse, duration: 0.108 ms
platform_key: graphql.validate, key: validate, duration: 0.537 ms
platform_key: graphql.analyze_query, key: analyze_query, duration: 0.123 ms
platform_key: graphql.analyze_multiplex, key: analyze_multiplex, duration: 0.159 ms
  Φόρτωση συνδέσμων (0,4 ms) SELECT "links".* FROM "links"
  ↳ app/graphql/graphql_tutorial_schema.rb:21:in `platform_trace'
platform_key: graphql.execute_query, key: execute_query, duration: 15.562 ms
  ↳ app/graphql/loaders/record_loader.rb:12:in `perform'
  ↳ app/graphql/loaders/association_loader.rb:46:in `preload_association'
platform_key: graphql.execute_query_lazy, key: execute_query_lazy, duration: 14.12 ms
platform_key: graphql.execute_multiplex, key: execute_multiplex, duration: 31.11 ms
Ολοκληρώθηκε 200 OK σε 48ms (Views: 1.2ms | ActiveRecord: 2.0ms | Allocations: 40128)

Περίληψη

GraphQL δεν είναι πλέον μια νέα τεχνολογία, αλλά οι λύσεις στα προβλήματά της δεν είναι πλήρως τυποποιημένες αν δεν αποτελούν μέρος της βιβλιοθήκης. Η εφαρμογή αυτής της τεχνολογίας στην έργο δίνει πολλές δυνατότητες αλληλεπίδρασης με το frontend και προσωπικά θεωρώ ότι αποτελεί μια νέα ποιότητα σε σχέση με αυτό που προσφέρει το REST API.

Γιατί θα πρέπει (πιθανώς) να χρησιμοποιήσετε την Typescript

Πώς να μην σκοτώσετε ένα έργο με κακές πρακτικές κωδικοποίησης;

Στρατηγικές άντλησης δεδομένων στο NextJS

Σχετικά άρθρα

Ανάπτυξη λογισμικού

Κατασκευάστε μελλοντικά ασφαλείς εφαρμογές Web: γνώσεις από την ομάδα εμπειρογνωμόνων του The Codest

Ανακαλύψτε πώς η The Codest υπερέχει στη δημιουργία κλιμακούμενων, διαδραστικών εφαρμογών ιστού με τεχνολογίες αιχμής, παρέχοντας απρόσκοπτη εμπειρία χρήστη σε όλες τις πλατφόρμες. Μάθετε πώς η τεχνογνωσία μας οδηγεί στον ψηφιακό μετασχηματισμό και την επιχειρηματική...

THECODEST
Ανάπτυξη λογισμικού

Top 10 εταιρείες ανάπτυξης λογισμικού με έδρα τη Λετονία

Μάθετε για τις κορυφαίες εταιρείες ανάπτυξης λογισμικού της Λετονίας και τις καινοτόμες λύσεις τους στο τελευταίο μας άρθρο. Ανακαλύψτε πώς αυτοί οι τεχνολογικοί ηγέτες μπορούν να βοηθήσουν στην ανύψωση της επιχείρησής σας.

thecodest
Λύσεις Enterprise & Scaleups

Βασικά στοιχεία ανάπτυξης λογισμικού Java: Α Guide to Outsourcing Successfully (Οδηγός για την επιτυχή εξωτερική ανάθεση)

Εξερευνήστε αυτόν τον βασικό οδηγό για την επιτυχή ανάπτυξη λογισμικού outsourcing Java για να αυξήσετε την αποδοτικότητα, να αποκτήσετε πρόσβαση στην τεχνογνωσία και να οδηγήσετε την επιτυχία των έργων με The Codest.

thecodest
Ανάπτυξη λογισμικού

Ο απόλυτος οδηγός για το Outsourcing στην Πολωνία

Η έξαρση της outsourcing στην Πολωνία οφείλεται στις οικονομικές, εκπαιδευτικές και τεχνολογικές εξελίξεις, που ευνοούν την ανάπτυξη της πληροφορικής και το φιλικό προς τις επιχειρήσεις κλίμα.

TheCodest
Λύσεις Enterprise & Scaleups

Ο πλήρης οδηγός εργαλείων και τεχνικών ελέγχου πληροφορικής

Οι έλεγχοι ΤΠ διασφαλίζουν ασφαλή, αποτελεσματικά και συμβατά συστήματα. Μάθετε περισσότερα για τη σημασία τους διαβάζοντας ολόκληρο το άρθρο.

The Codest
Jakub Jakubowicz CTO & Συνιδρυτής

Εγγραφείτε στη βάση γνώσεών μας και μείνετε ενήμεροι για την τεχνογνωσία από τον τομέα της πληροφορικής.

    Σχετικά με εμάς

    The Codest - Διεθνής εταιρεία ανάπτυξης λογισμικού με κέντρα τεχνολογίας στην Πολωνία.

    Ηνωμένο Βασίλειο - Έδρα

    • Γραφείο 303B, 182-184 High Street North E6 2JA
      Λονδίνο, Αγγλία

    Πολωνία - Τοπικοί κόμβοι τεχνολογίας

    • Πάρκο γραφείων Fabryczna, Aleja
      Pokoju 18, 31-564 Κρακοβία
    • Πρεσβεία του εγκεφάλου, Konstruktorska
      11, 02-673 Βαρσοβία, Πολωνία

      The Codest

    • Αρχική σελίδα
    • Σχετικά με εμάς
    • Υπηρεσίες
    • Case Studies
    • Μάθετε πώς
    • Καριέρα
    • Λεξικό

      Υπηρεσίες

    • Συμβουλευτική
    • Ανάπτυξη λογισμικού
    • Backend Ανάπτυξη
    • Ανάπτυξη Frontend
    • Staff Augmentation
    • Backend Developers
    • Μηχανικοί cloud
    • Μηχανικοί δεδομένων
    • Άλλα
    • Μηχανικοί QA

      Πόροι

    • Γεγονότα και μύθοι σχετικά με τη συνεργασία με εξωτερικό συνεργάτη ανάπτυξης λογισμικού
    • Από τις ΗΠΑ στην Ευρώπη: Γιατί οι αμερικανικές νεοσύστατες επιχειρήσεις αποφασίζουν να μετεγκατασταθούν στην Ευρώπη
    • Σύγκριση υπεράκτιων κόμβων ανάπτυξης τεχνολογίας: Ευρώπη (Πολωνία), ASEAN (Φιλιππίνες), Ευρασία (Τουρκία)
    • Ποιες είναι οι κορυφαίες προκλήσεις των CTOs και των CIOs;
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Website terms of use

    Πνευματικά δικαιώματα © 2025 από The Codest. Όλα τα δικαιώματα διατηρούνται.

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