Статьи

Обработка сложных объектов, устойчивость и обмен сообщениями на мобильных устройствах

Постоянство данных и обмен сообщениями — очень распространенная задача, которая вам почти наверняка понадобится практически во всех ваших приложениях. Мобильные платформы прошли долгий путь в поддержке устойчивости данных, в основном благодаря движку SQLite. Это стало стандартом на всех мобильных платформах. Тем не менее, это все еще очень легкий двигатель и не дает вам все возможности, как SQL-сервер. Это даже не должно иметь место на мобильном устройстве, где постоянные данные предназначены главным образом для кэширования, пока данные не достигнут своего конечного пункта назначения где-нибудь на сервере. Имея это в виду, как мы можем сохранить сложные объекты на мобильной платформе? Как мы управляем сообщениями о сложных объектах? И это именно то, что мы обсудим в этом сообщении в блоге. Это не введение в то, как использовать ORM для хранения некоторых объектов в базе данных,скорее, это то, как обрабатывать сложные отношения между объектом данных и его потомками или братьями и сестрами при хранении или передаче этой части данных.

SQLite.NET PCL

Я давно занимаюсь разработкой мобильных платформ, и мне очень нравится использование SQLite.NET PCL . Это очень простая и легкая ORM, созданная на исходном SQLite.NET с добавленной поддержкой PCL (Portable Class Library). Эта библиотека позволяет очень легко хранить данные в базе данных на мобильном устройстве, и она предоставляет вам единый API на всех платформах для выполнения всех задач, связанных с сохранением данных. Поэтому, если вы не использовали это раньше, я настоятельно рекомендую вам взглянуть на это, и в этом сообщении в блоге предполагается использование этой платформы для локального хранения данных.

Versioning

Если ваше мобильное приложение предназначено для создания списка дел, то, возможно, не составит труда потерять одну запись здесь или там или разрешить конфликтующие элементы, выбрав последнюю, я говорю это и знаю, что вы можете потерять некоторых пользователей, если вы это сделаете :). Однако что делать, если ваше мобильное приложение было связано с управлением чрезвычайными ситуациями или использовалось врачами. Это делает необходимым предварительное определение логики хранения элементов, разрешения конфликтов, и вам необходимо всегда хранить все биты информации на всех устройствах. У меня было подобное требование, когда мне нужно было поддерживать управление версиями всех элементов и определить, как будет разрешаться конфликт. Таким образом, я решил использовать управление версиями для своих объектов данных. Этот дизайн принимает следующие решения:

  1. Любое изменение будет применено к объектам данных как журнал изменений.
  2. Журналы изменений глобально и уникально идентифицируются через уникальные идентификаторы.
  3. Объекты данных будут иметь атрибут VERSION, который является уникальным. Этот атрибут VERSION будет ссылаться на самый последний идентификатор журнала изменений.
  4. Каждый объект данных будет иметь список журналов изменений в упорядоченном списке. Порядок этих журналов представляет собой график времени, когда эти журналы изменений были применены.
  5. Для простоты предположим, что у объекта данных есть список свойств / атрибутов, которые будут представлены в виде таблицы пар ключ / значение.
  6. Другие отклонения / предположения о дизайне будут игнорироваться ради этого поста в блоге. Такое решение может включать сохранение типа журналов изменений (изменение, созданное пользователем, результат объединения данных и т. Д.), Сохранение других данных безопасности (аутентификация / авторизация) для каждого элемента данных, сохранение других метаданных для каждого объекта данных, например, кто изменился. что и когда.

Имея это в виду, наша диаграмма объекта данных должна выглядеть примерно так:

UML Диаграмма нашего базового дизайна элементов (версий)

Внедрение изменений на основе версий

Теперь, когда мы собрали наш базовый дизайн, мы должны реализовать его так, чтобы команда разработчиков могла безопасно работать над ним. Мы не можем предполагать, что спросим членов команды: «Эй, не могли бы вы использовать этот метод, когда вы пытаетесь применить некоторые изменения, потому что это необходимо?», Подумайте, это сработает? :). Я знаю, вы должны думать, что сейчас это абсурдно, но я видел это в некоторых командах. Или, если они не скажут это таким образом, они будут полагаться на некоторые комментарии в коде или другие вики / документацию. Мой подход состоит в том, чтобы сделать его надежным и позволить самому документу проекта. Я не должен объяснять это людям. Разработчики (члены моей команды) должны иметь возможность использовать это, не беспокоясь о внутренней реализации. Для этого нам нужно следующее:

1. Свойства только для чтения

Чтобы гарантировать, что мы не собираемся изменять какое-либо свойство нашего объекта данных без использования журналов изменений, свойства должны быть доступны только для чтения. Это гарантирует, что мы не сможем создать новую версию элемента без использования метода applyChangeLog (log) или конструктора.

2. Полностью определенный конструктор (ы)

Мы не можем предоставить конструктор, который позволил бы потребителю нашей платформы создавать / создавать экземпляр объекта данных без указания всех его атрибутов. Следовательно, наши конструкторы должны определять все свойства объекта во время создания.

3. Легкая композиция

Наша структура (объект данных) должна иметь простой способ создания объектов из сериализованной версии или из другой версии элемента. Это очень необходимо для хранения этих объектов данных в базе данных или при попытке отправить их по сети.

При всем этом наша базовая реализация объекта может выглядеть примерно так:

  // our change log first
  public class ChangeLog 
  {
     public string Id {get;set;}
     public int Order {get;set;}
     public string CreatedBy {get;set;}
     public ChangeLogType Type {get;set;}
     public DateTime CreatedOn {get;set;}
     public string ParentId { get; set; }
     public Dictionary<string, object> ChangingAttributes {get;set;}
   }

   public class RegisterItem : ModelBase
   {
     public RegisterItem (string id, string name) 
             : this(id, name, string.Empty, new Dictionary<string,object>(), new List<ChangeLog>())
     {
     }

     public RegisterItem (string id, string name, 
                          string version, Dictionary<string, object> attributes, List<ChangeLog> changeLogs)
     {
        Id = id;
        name = name;
        _version = version;
        _attributes = attributes;
        _changeLogs = changeLogs;
     }

     // This is needed for the internal use (serialisation/De-serialisation, and db storage).
     // Bcoz this ctor is Obsolete, it will through a warning and the warn will be escalated to an error if used.     
     [Obsolete("This ctor is only for the deserialiser and the db layer. Use other ctor with full params")]
     public RegisterItem ()
     {
     }

     public string Version { get {return _version;} private set { _version = value;}  }
     private string _version {get; set;}

     public string Name { get{ return _name; } private set { _name = value;}  }
     private string _name { get; set; }

     [Ignore]
     public List<ChangeLog> ChangeLogs { get { return _changeLogs; } private set { _changeLogs = value;}  }
     private List<ChangeLog> _changeLogs { get; set; }

     [Ignore]
     public Dictionary<string, object> Attributes {get{return _attributes; } private set { _attributes = value;}}
     private Dictionary<string, object> _attributes { get; set;}
   }

Пока все хорошо, пока мы реализовали наши объекты данных с базовыми версиями своих дочерних объектов. Теперь вопрос в том, как сохранить это в базе данных и как сериализовать / десериализовать объект, чтобы отправить его по сети. Это на самом деле вторая сложная часть, :)потому что, если вы работали с SQLite.Net раньше, вы бы знали, что он предназначен для разработчиков мобильных приложений для хранения простых объектов и основных типизированных атрибутов в базе данных. Для нашего сценария у нас есть сложные объекты с дочерними объектами и другими сложными атрибутами (словарь).

Хранение в базе данных SQLite

Наша база данных будет хранить основную информацию об объектах данных (имя, идентификатор, версия) вместе с полной копией объекта, который сериализован в базовый тип, такой как строка (или может быть двоичным, если хотите). Чтобы сделать это простым и понятным для наших потребителей (разработчиков, использующих этот API), мы добавили свойство к объекту данных, которое называется AsJson . Это свойство будет сериализовать и хранить полную копию объекта, когда объект сохраняется в базе данных, а когда объект создается из его базовых атрибутов, он заполняет другие свойства (например, дочерние объекты и другие сложные свойства (например, Словарь)). Простая реализация этого свойства может выглядеть примерно так:

[JsonIgnore]
public string AsJson 
{
  get
  {
     var json = MySerialiser.ToJson(this);
     return json;
  } 
  set
  {
     var json = value;
     if (!string.IsNullOrEmpty(json))
     {
         var originalObject = MySerialiser.LoadFromJson<RegisterItem>(json);
         if (originalObject != null)
         {
            //We could use something like AutoMapper here
            _changeLogs = originalObject.ChangeLogs;
            _attributes = originalObject.Attributes;
         }
      }
  }
}

Как вы можете видеть, само свойство (AsJson) игнорируется при сериализации в Json, потому что оно будет циклически зависимым и не будет работать. Кроме того, наши разработчики должны будут что-либо делать при хранении или извлечении из / в базу данных. Наше свойство AsJson выполнит эту работу и сохранит / построит наши элементы в / из базы данных. Также обратите внимание, как у нас был атрибут [Ignore] в наших сложных дочерних объектах. Это относится к нашему ORM (SQLite.Net), что подразумевает отсутствие необходимости хранить эти объекты в базе данных.

обмен сообщениями

Пару месяцев назад я выступил в DDD Melbourne с докладом об обмене сообщениями в сценариях Peer-2-Peer на мобильных устройствах. Вы можете найти колоду слайдов здесь . И для этого конкретного проекта мне нужно было иметь возможность сериализовать / десериализовать мой объект данных, чтобы отправить их через мои P2P-соединения другой стороне. Это стало намного проще благодаря свойству, которое мы обсуждали ранее и называется AsJson . Единственная сложность заключается в том, что при сериализации необходимо изменить настройки по умолчанию вашего сериализатора Json, так как он должен иметь возможность сериализации / десериализации частных свойств объектов данных. Предполагая, что мы используем что-то вроде Newtowonsoft.Json, наш сериализатор будет примерно таким:

   var resolver = new PrivateSetterContractResolver();
   var settings = new JsonSerializerSettings{ ContractResolver = resolver };
   var obj = JsonConvert.DeserializeObject<T>(input, settings);

Вот и все. Надеюсь, что вы найдете это полезным, и вы выбрали несколько идей о том, как обрабатывать хранение и обмен сообщениями сложных объектов данных на мобильных устройствах. Если у вас есть вопрос, комментарии или, возможно, предложение сделать что-то по-другому / лучше, я хотел бы услышать от вас, поэтому свяжитесь с нами.