Go — отличный язык. Да, я планирую каждую статью о Go начинать с этого предложения. Одним из больших преимуществ Go является его строгая типизация. Если у вас есть int и int32, вы не можете просто назначить их друг другу. Это позволяет вам строго относиться к своим данным и помогает поддерживать общую гигиену кода.

Конечно, помимо случайного преобразования типов, у строго типизированных языков есть недостатки. В Go, например, нет союзов. Союзы практичны, если у нас есть данные, которые могут принимать различные формы.

MongoDB, с другой стороны, отлично справляется с такими данными. Если вы хотите, вы можете придать каждому документу в коллекции MongoDB другую структуру! Я бы не хотел управлять этой базой данных, но это возможно!

Для более реалистичного примера предположим, что мы хотим создать базу данных, содержащую транспортные средства. Мы хотим хранить все транспортные средства, которые мы видим на улице, в воздухе, в новостях, с некоторыми данными для каждого транспортного средства.

Например, когда мы садимся в машину, мы хотим знать, что это 2-дверный Ford Mustang, работающий на обычном топливе. Или что это 4-дверная Tesla Model S, электрическая.

Для самолета мы хотим сохранить, что это Боинг 747–400 на 400 мест или Cessna 172 на 4 места. Для космического корабля нам просто нужно знать название и страну его происхождения.

Это дает нам следующую структуру (в псевдокоде):

Vehicle: {
  Union {
    Plane {
      BrandName
      Type
      Seats
    }
    Spacecraft {
      Name
      Country
    }
    Car {
      BrandName
      Type
      Fuel
      Doors
    }
  }
}

Как закодировать это в Go

Мы не можем использовать такой союз в Go. Но вместо этого мы можем использовать карту. Это дает нам такую ​​структуру:

Vehicle: {
  A map with the following possibilities:
  EITHER { "BrandName", "Type", "Seats" }
  OR { "Country" }
  OR { "BrandName", "Type", "Fuel", "Doors" }
}

Теперь нам нужно убедиться, что мы получаем правильные поля в зависимости от того, автомобиль это, самолет или космический корабль. Для этого я также сохраняю название схемы в своем автомобиле. Scheme будет содержать либо Plane, либо Spacecraft, либо Car.

Это дает нам следующее определение транспортного средства:

// DATABASE VEHICLES
type Vehicle struct {
 Id bson.ObjectId   `bson:”_id,omitempty” json:”objectid”`
 Scheme string      `bson:”scheme” json:”data”`
 Data VehicleData   `bson:”data” json:”data”`
}
type VehicleData map[string]string

Как видите, я явно добавил идентификатор MongoDB и все имена полей bson/json.

Итак, если у нас есть машина, мы можем получить доступ к количеству дверей, используя Data["Doors"]="5".

Чтобы облегчить себе жизнь, я создал список типов транспортных средств в другой коллекции MongoDB под названием VehicleSchemes. Эта коллекция содержит документ для каждого типа транспортного средства, в котором перечислены все поля, необходимые для этого типа транспортного средства. Вот что в нем:

{
  "name": "Plane",
  "fields": {
    "1": "BrandName",
    "2": "Type",
    "3": "Seats"
  }
}
{
  "name": "Spacecraft",
  "fields": {
    "1": "Name"
    "2": "Country"
  }
}
{
  "name": "Car",
  "fields": {
    "1": "BrandName",
    "2": "Type",
    "3": "Fuel",
    "4": "Doors"
  }
}

Как видите, у нас всегда есть схема name, за которой следует список fields. Каждое из полей пронумеровано и содержит название поля.

В нашей программе Go мы будем считывать эту коллекцию и использовать ее для заполнения наших транспортных средств. Для этого я определил тип:

// DATABASE VEHICLESCHEMES:
type VehicleMongoScheme struct {
 Name string `bson:”name” json:”name”`
 Fields map[string]string `bson:”fields” json:”fields”`
}

Я тоже пользуюсь картой. Это делает мой код простым, используя только карты.

Итак, мы готовы приступить к кодированию! Я собираюсь пропустить разделы package main и import в приведенном ниже коде. Если вам нужен код, вы можете найти его в проекте Github, который я создал для этой статьи.

В нашей основной процедуре сначала мы определяем массив для хранения схем транспортных средств:

func main() {
 var theDbSchemes []VehicleMongoScheme

Далее мы открываем соединение с базой данных и открываем коллекцию VehicleScheme:

 session, err := mgo.Dial(“localhost”)
 if err != nil {
   panic(err)
 }
 defer session.Close()
 d := session.DB(“test”).C(“VehicleSchemes”)

Теперь читаем все схемы сразу и выводим сколько нашли:

 err = d.Find(bson.M{}).All(&theDbSchemes)
 if err != nil {
   fmt.Println(“Error occured getting the schemes”)
   panic(0)
 }
 fmt.Printf(“Fetched %d schemes\n”, len(theDbSchemes))

Ну вот! Наш массив theDbSchemes, который мы определили выше, теперь содержит описания всех известных нам типов транспортных средств. В моем примере у меня было 3 схемы: одна для автомобиля, самолета и космического корабля. Итак, массив theDbSchemes содержит 3 записи.

В оставшейся части кода примера показано, как заполнить файл Vehicle. Нам нужно определить тип транспортного средства, а затем применить правильную схему для заполнения правильных полей. В примере я делаю это в цикле для целей тестирования и получаю случайное число для схемы:

c := session.DB(“test”).C(“Vehicle”)
for counter := 0; ; counter++ {
 fmt.Println(“Vehicle “, counter)
 // Fill the Data field with a random schema
 theSchemeNr := time.Now().Nanosecond() % len(theDbSchemes)
 theId := bson.NewObjectId()
 theData := make(map[string]string)

Не забудьте сначала make карту.

Чтобы заполнить все поля, мы используем цикл от 1 до количества полей в схеме. Мы можем найти количество полей, взяв len массива полей в нашей текущей схеме:

 for i := 1; i < len(theDbSchemes[theSchemeNr].Fields)+1; i++ {

Теперь заполняем все поля внутри этого цикла. Значение поля просто случайное.

   theFieldNumber := strconv.Itoa(i)
   theFieldName := theDbSchemes[theSchemeNr].Fields[theFieldNumber]
   theValue := “Test” + strconv.Itoa(counter)
   theData[theFieldName] = theValue
 }

После того, как мы заполнили все поля, мы готовы заполнить наш автомобиль:

theVehicle := Vehicle{
  Id: theId,
  Scheme: theDbSchemes[theSchemeNr].Name,
  Data: theData,
}

Наконец, мы можем сохранить наш автомобиль в базе данных Mongo и снова запустить цикл, чтобы создать новый автомобиль:

  err = c.Insert(theVehicle)
  if err != nil {
    fmt.Println(“Error occured inserting vehicle”)
    panic(0)
  }
 } // end of the for counter loop
}  // end of func main

Резюме

Для реализации полиморфизма в Go мы использовали карты. Мы можем легко использовать карты с MongoDB, поэтому они идеально подходят для этой цели. Это позволяет нам хранить космический корабль как { "Name" : "Space Shuttle", "Country" : "USA" }, а заботу как { "BrandName" : "Tesla", "Type" : "S", "Fuel" : "Electric", "Doors" : "4" }.

Полиморфный тип Vehicle может содержать автомобили, самолеты и космические челноки. Для этого мы храним все эти поля на карте. Мы добавили поле Scheme, чтобы указать, какую схему использовать. Схема транспортного средства содержит название схемы (автомобиль, самолет или космический корабль) и список всех полей, которые нам нужны для этого типа транспортного средства.

Поскольку мы все равно используем MongoDB, мы создали вторую коллекцию, содержащую все наши схемы. Это позволяет легко расширить его в будущем. Если мы хотим добавить велосипед, мы просто вставляем эту схему в коллекцию VehicleSchemes, перечисляя поля, необходимые для идентификации велосипеда.

Полный исходный код этого примера можно найти на Github: https://github.com/PeterLeyssens/GoMongoSchemes