1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/winal-Scree

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Содержание

一、Scree — для решения каких проблем?

Чтобы ответить на этот вопрос, необходимо упомянуть о разработке без использования ORM и трёхслойной архитектуре.

Трёхслойная архитектура

Не будем подробно останавливаться на трёхслойной и N-слойной архитектурах, но стоит отметить, что в трёхслойной архитектуре слой доступа к данным не обязательно является одним слоем, он также может быть двумя, тремя или даже N слоями. В режиме разработки без использования ORM бизнес-логика реализуется через написание SQL-запросов (часто в виде представлений или хранимых процедур), а затем вызывается слой доступа к данным для чтения и записи данных.

На основе нижнего слоя ORM-фреймворка (здесь необходимо добавить определение нижнего уровня, с развитием технологий и расширением масштабов системы любой уровень в горизонтальной и вертикальной плоскостях имеет многоуровневое расширение), основная цель состоит в том, чтобы заменить слой доступа к данным и одновременно преобразовать операции бизнес-логики в слое доступа к данным в операции объектно-ориентированного подхода, что также является первоначальным намерением Scree.

Преимущества объектно-ориентированных операций очевидны, и каждый, кто их использует, знает об этом. Появление ORM полностью объективизировало процесс построения систем на основе объектно-ориентированных языков, устранив недостатки эпохи SQL. Данные наконец стали объектами, отдельными или группами, позволяя разработчикам сосредоточиться на бизнес-логике, обеспечивая более эффективное командное сотрудничество, ускоренную миграцию данных и более гибкое расширение системы.

Два, Scree для замены слоя доступа к данным — как это было задумано? Что было сделано?

  1. Автоматическое создание таблиц

Поскольку мы говорим о флаге ORM, поддержание соответствия между классами и таблицами, а также объектами и данными строк является основным требованием. Рассмотрим следующий фрагмент кода:

public enum NewsType
{
    Military = 0,
    World = 1,
    Society = 2,
    Culture = 3,
    Travel = 4,
}
public class News : SRO
{
    [StringDataType(IsNullable = false, Length = 50)]
    public string Title { get; set; }

    [StringDataType(IsMaxLength = true)]
    public string Context { get; set; }

    public string Author { get; set; }

    public NewsType Type { get; set; }

    public int ReadingQuantity { get; set; }

    static News()
    {
        TimeStampService.RegisterIdFormat<News>("xw{0:yyMMdd}{1}");
    }
}

— Scree автоматически создаст таблицу с тем же именем, что и класс, наследуемый от SRO (поддержка различных имён таблиц не предусмотрена, это не техническая проблема, скорее изначально был такой дизайн, после тщательного рассмотрения эта поддержка была отменена. Меньше значит больше, в стремлении к максимальной простоте и удобству использования). — Поддерживает только SQL Server, если есть другие требования к базе данных, рекомендуется изменить Scree.DataBase.SQLServer. — Поддерживаются типы данных int, bool, string, DateTime, перечисление, decimal, long, byte[], соответствующие типам данных в базе данных int, bit, nvarchar или text, datetime, int, decimal, bigint, image. — Можно использовать атрибут для указания подробной информации о типе поля, см. Scree.Attributes. — По умолчанию строка представляет собой nvarchar(32), десятичное число — decimal(18,4). — Система автоматически создаёт таблицы только для новых классов, при изменении полей требуется ручное изменение базы данных. — В базовом классе SRO предусмотрены пять стандартных атрибутов Id, CreatedDate, LastAlterDate, Version, IsDeleted, то есть все объекты данных Scree будут иметь эти пять полей, их использование будет объяснено позже.

  1. Вставка, обновление, удаление и запрос

Основные операции с данными включают вставку, обновление, удаление и запрос, ниже показано, как удобно читать и записывать данные, работая с объектами.

  • Вставка:
//Если напрямую new также возможно, в настоящее время эквивалентно, рекомендуется использовать CreateObject единообразно, в будущем можно использовать CreateObject для некоторых вещей
//News news = new News();
News news = PersisterService.CreateObject<News>();

news.Title = "Заголовок новости";
news.Context = "Содержание новости";

PersisterService.SaveObject(news);
  • Запрос:
News obj = PersisterService.LoadObject<News>("Идентификатор новости");
  • Обновление:
News obj = PersisterService.LoadObject<News>("Идентификатор новости", LoadType.DataBaseDirect);

news.Title = "Новый заголовок";
news.Context = "Новое содержание";

PersisterService.SaveObject(news);
  • Удаление, поддерживает только логическое удаление, не выполняет физическое удаление, которое можно понимать как удаление само по себе является модификацией. Логически удалённые данные автоматически скрываются при запросе через фреймворк.
News obj = PersisterService.LoadObject<News>("Идентификатор новости", LoadType.DataBaseDirect);
news.IsDeleted = true;
PersisterService.SaveObject(news);

По сути, вставка и удаление имеют только два действия: чтение и запись. В Scree одиночный объект читается с помощью LoadObject, записывается с помощью SaveObject. При реальной обработке бизнеса, если объект, загруженный с помощью LoadObject, предназначен для последующего сохранения с помощью SaveObject, параметр LoadObject должен использовать LoadType.DataBaseDirect (режим загрузки без кэша).

  1. Чтение группы объектов

Используйте LoadObjects для точного поиска, приблизительного поиска и сортировки.

internal static News[] GetNewsByType(NewsType type, LoadType loadType)
{
    List<IMyDbParameter> prams = new List<IMyDbParameter>();
    prams.Add(DbParameterProxy.Create("Type", SqlDbType.Int, (int)type));

    News[] objs = PersisterService.LoadObjects<News>("[Type]=@Type", prams.ToArray(), loadType);

    return objs;
}
internal static News[] GetNewsByAuthor(string author, LoadType loadType)
{
    List<IMyDbParameter> prams = new List<IMyDbParameter>();
    prams.Add(DbParameterProxy.Create("Author", SqlDbType.NVarChar, "%" + author + "%"));

    News[] objs = PersisterService.LoadObjects<News>("[Author] like @Author order by ReadingQuantity desc", 
    prams.ToArray(), loadType);

    return objs;
}
  1. Сохранение группы объектов

По умолчанию используется транзакционное сохранение.

News news =
``` **Контекст хранения «userdb»**
127.0.0.1 AdvancedExample-User dbname dbpassword true 60 1 100 ```

Конфигурация сопоставления

Конфигурация сопоставления используется для определения того, какой объект будет соответствовать какой базе данных. Например:

- mapping.config используется для конфигурации того, какому объекту соответствует какая база данных, например:
userdb userdb ```

Вышеуказанная конфигурация означает, что по умолчанию база данных объекта User — storage.config, которая называется current. Его псевдоним (alias) соответствует базе данных userdb в storage.config.

Примечание: Если в mapping.config не указано имя типа, то его база данных по умолчанию — storage.config с именем current. Если нет конфигурации с именем current, то база данных по умолчанию — первая база данных в storage.config.*

Разделение таблиц

По мере увеличения объёма данных одной таблицы разделение таблиц становится обычным решением. Ранее упоминалось, что BeforeSave может выполнять некоторые действия. Ниже приведён пример:

public class User : SRO
{
    protected override void BeforeSave()
    {
        this.RegisterStorageBehavior(null);
        this.RegisterStorageBehavior("UserSubById", "UserById" + Id.Substring(Id.Length - 1));
        this.RegisterStorageBehavior("UserSubByHour", "UserByHour" + CreatedDate.Hour.ToString());
    }
}

Фреймворк предоставляет удобный интерфейс регистрации поведения хранилища.

this.RegisterStorageBehavior("UserSubById", "UserById" + Id.Substring(Id.Length - 1));

Метод RegisterStorageBehavior принимает два параметра: псевдоним хранилища и имя таблицы после разделения. В этом примере используется последний символ идентификатора для разделения, из определения идентификатора видно, что последний символ идентификатора — это число от 0 до 9, это означает, что данные объекта User будут разделены на 10 таблиц, начиная с UserById0 и заканчивая UserById9.

this.RegisterStorageBehavior("UserSubByHour", "UserByHour" + CreatedDate.Hour.ToString());

Аналогично, объект User разделяется по времени создания, можно получить 24 таблицы, начиная с UserByHour0 и заканчивая UserByHour23, используя псевдоним UserSubByHour, который также хранится в базе данных AdvancedExample-User.

this.RegisterStorageBehavior(null);

Псевдоним равен null, и таблица не указана, поэтому данные объекта User будут храниться в общей таблице в базе данных с именем AdvancedExample.

В этом примере объект User создаст три копии одних и тех же данных. Это всего лишь пример, вы можете настроить поведение хранения объектов в соответствии с вашими бизнес-потребностями.

Примечание: Разделение таблиц не происходит автоматически, оно требует ручного создания.*

Дополнительные возможности объектов SRO

  • CurrentAlias

Поскольку существует разделение объектов по хранилищу, один и тот же объект может быть прочитан из разных баз данных, свойство CurrentAlias объекта представляет источник псевдонима хранилища объекта.

  • CurrentTableName

Подобно разделению таблиц, CurrentTableName представляет собой таблицу источника объекта.

  • SaveMode
public enum SROSaveMode
{
    //Новый или новый Load
    Init = 0,
    //Объект создан впервые и сохранён
    Insert = 1,
    //Существующий объект сохраняется снова
    Update = 2,
    //Существующий объект пытается сохраниться через SaveObject, но объект не был изменён в процессе бизнес-обработки
    NoChange = 3,
}

Указывает режим последнего сохранения объекта, с помощью этого свойства и предыдущего AfterSave можно выполнить некоторые операции в зависимости от потребностей бизнеса.

  • GetOriginalValue

После загрузки объекта через бизнес-логику некоторые атрибуты могут быть изменены, через GetOriginalValue можно получить исходное значение атрибута перед изменением. На самом деле, внутри фреймворка сравнение текущего значения с исходным значением используется для генерации SQL-операторов обновления. Предыдущий SROSaveMode.NoChange также основан на этом.

Пользовательские сервисы

Фреймворк обеспечивает функцию внедрения пользовательских сервисов, которые автоматически загружаются при запуске системы. Возьмём в качестве примера InitService.

  • InitService должен наследовать ServiceBase и переопределять методы Init() и Run() в соответствии с потребностями. Метод Run() выполняется после завершения всех методов Init().

  • В root.config сервис внедряется следующим образом:

<Service type="MyApp.Services.InitService, MyApp.Services" driver="MyApp.Services.InitService, MyApp.Services"/>

Автоматическое создание снимков объектов

Снимок — это копия данных, используемая для резервного копирования и восстановления данных. У меня есть смелая идея: позволить системе восстанавливать данные до любого состояния. Чтобы реализовать эту идею, необходимо записывать все исходные данные и последующие изменения, чтобы можно было восстановить данные в любое время путём обратного выполнения операций. Конечно, это только теория, на практике инженерные затраты и затраты на хранение всё ещё очень велики, даже невозможны. Однако с точки зрения небольших деталей, если мы сможем записать изменения данных, это станет мощным инструментом для устранения неполадок в нашей системе. Раньше мы записывали большое количество журналов различными способами в бизнес-логике, но эти журналы часто были фрагментарными и локальными, и когда возникали проблемы, информации в журналах было недостаточно, не говоря уже о том, что эти журналы в основном записывают операционное поведение, а не сами данные.

Ниже приводится простое введение в автоматическое создание снимков объектов.

PersisterService.RegisterAfterSaveMothed(SROLogging);

Через седьмой пункт внедряемый сервис регистрирует поведение после сохранения при запуске.

Ранее упоминалось, что RegisterAfterSaveMothed будет выполняться после вызова метода SaveObject, через этот момент внедрения можно получить ссылку на только что сохранённый объект для выполнения некоторых операций.

private static void SROLogging(SRO[] objs)
{
    if (objs == null)
    {
        return;
    }

    Thread t = new Thread(SROLoggingThreadMothed);
    t.Start(objs);
}

private static void SROLoggingThreadMothed(object o)
{
    try
    {
        SRO[] objs = (SRO[])o;

        LogService.SROLogging(objs);
    }
    catch (Exception ex)
    {
        LogProxy.Error(ex, false);
    }
}

internal static void SROLogging(SRO[] objs)
{
    try
    {
        List<SRO> list = new List<SRO>();
        SROLog log;
        foreach (SRO obj in objs)
        {
            if (obj == null || obj.SaveMode == SROSaveMode.Init || obj.SaveMode == SROSaveMode.NoChange
                || !(obj is INeedFullLogging))
            {
                continue;
            }

            log = new SROLog();
            log.ObjectId = obj.Id;
            log.ObjectType =
``` ```
obj.GetType().FullName;
log.ObjectJson = JsonConvert.SerializeObject(obj);
log.HostName = Tools.GetHostName();
list.Add(log);
}

if (list.Count > 0)
{
PersisterService.SaveObject(list.ToArray());
}
}
catch (Exception ex)
{
LogProxy.Error(ex, false);
}

Через вышеуказанные простые операции все объекты, наследующие интерфейс INeedFullLogging, SRO, будут автоматически создавать копии данных при каждом сохранении, которые будут храниться в таблице SROLog (конечно, также можно разделить на разные базы данных и таблицы, но это не обсуждается здесь). Таким образом, все версии изменений объекта будут зарегистрированы, что очень полезно для поиска ошибок.

Это лишь введение, и есть ещё много тонкостей, которые предстоит изучить.

Пять: среда распределённой системы

1. Синхронизация

Синхронизация в основном предназначена для локального кэша.

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

Например, информация о товарных запасах в электронной коммерции часто отображается в верхней части всей системы. Информация о товарах естественно будет кэшироваться, и её следует кэшировать как можно дольше. Но информация о запасах товаров постоянно меняется, и покупатели могут видеть, что запасов много, но когда они собираются разместить заказ, они обнаруживают, что запасы закончились, что создаёт плохой опыт покупок. Конечно, можно отдельно обрабатывать информацию о запасах, но цены, названия и т. д. также могут меняться. Для самообслуживания B2C это ещё можно контролировать в определённой степени, но для C2C изменения становятся более сложными, и это проблема разделения внимания, которую необходимо учитывать для различных объектов.

  • Механизм синхронизации

Ⅰ. После изменения объекта и сохранения его в базу данных асинхронно отправляется пакет данных синхронизации, содержащий информацию об объекте, типе, идентификаторе и версии.

Ⅱ. Сервер приложений периодически извлекает пакеты данных синхронизации с сервера синхронизации.

Ⅲ. При загрузке объекта, если он загружается из кэша, необходимо проверить пакет данных синхронизации. Если объект был изменён и версия пакета данных синхронизации выше текущей версии кэша, то данные необходимо извлечь снова из базы данных.

Ⅳ. Фреймворк предоставляет ленивый и нетерпеливый режимы извлечения данных, по умолчанию используется нетерпеливый режим, подробности см. в synclient.config.

Фреймворк по умолчанию поддерживает только один сервер синхронизации, хотя это и является проблемой, но она не является фатальной. Для большинства систем этого достаточно, и фреймворк не углубляется в эту тему, исходя из принципа «простота использования». Если действительно необходимо расширить сервер синхронизации, архитекторы могут сделать некоторые расширения на этой основе.

  • Начало использования службы синхронизации

Ⅰ. Создайте службу Scree.SynServerService (это служба синхронизации, которая может быть развёрнута на отдельном сервере или вместе с приложением), рекомендуется установить автоматический запуск.

Ⅱ. Клиент ссылается на Scree.Syn.Client и добавляет synclient.config в конфигурацию.

Ⅲ. В root.config клиента добавьте регистрацию службы. <Service type="Scree.Syn.Client.ISynClientService, Scree.Syn.Client" driver="Scree.Syn.Client.SynClientService, Scree.Syn.Client"/>

2. Распределённая блокировка

Что касается распределённых блокировок, я действительно колебался. Это палка о двух концах, особенно беспокоюсь, что ею злоупотребляют, а то, чего боишься, обязательно произойдёт.

{
get
{
return ServiceRoot.GetService<ILockService>();
}
}


ILockItem[] items = new ILockItem[]{
LockService.CreateLockItem<User>("UserId_1"),
LockService.CreateLockItem<User>("UserId_2"),
LockService.CreateLockItem<SystemLog>("LogId_1")
};

string lockId;
bool isLockGetted = LockService.GetLock(items, out lockId);
if (isLockGetted)
{
try
{
//Бизнес-логика
}
finally
{
LockService.ReleaseLock(lockId);
}
}```
Из параметров LockService.GetLock видно, что это массив, то есть вы можете одновременно блокировать несколько объектов.

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

Сценарии использования распределённых блокировок очевидны, и примеры проектов их не приводят. Вы естественным образом подумаете об этом, когда вам это действительно понадобится, и не будете знать, хорошо ли это, если вам это не нужно.

* Начните использовать службу распределённой блокировки

Ⅰ. Создайте Scree.LockServerService (служба блокировки, которая может быть развернута на отдельном сервере или совместно с приложением), рекомендуется установить автоматический запуск.

Ⅱ. Клиентская ссылка Scree.Lock.Client, добавление lockclient.config в конфигурацию.

Ⅲ. Регистрация службы в root.config клиента.
```<Service type="Scree.Lock.ILockService, Scree.Lock" driver="Scree.Lock.Client.LockService, Scree.Lock.Client"/>```


### Шесть: сборник распространённых проблем


 **1. Почему таблица не создаётся автоматически?**

- Убедитесь, что конфигурация storage.config верна.
- Проверьте, имеет ли атрибут autocreatetable в mapping.config значение true.
- Проверьте файл TableCreated.config, чтобы убедиться, что соответствующая таблица уже существует в базе данных. Если вы хотите повторно создать таблицу, удалите соответствующую запись конфигурации при удалении таблицы и перезапустите приложение.
- Убедитесь, что имя сборки соответствующего класса настроено в узле Assembly файла mapping.config.
- Если существует разделение на несколько баз данных, проверьте, создана ли таблица в других базах данных. Если да, проверьте конфигурацию таблицы.
- Разделение таблиц не будет создано автоматически.

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Это ORM-фреймворк, разработанный на C#, который не содержит конкретной бизнес-логики. Объекты основаны на контроле версий, включают интеграцию транзакций, кэширования, синхронизации и блокировки. Автор стремился найти баланс между простотой, практичностью, расширяемостью и распределённостью. Это не просто ORM, а концепция базовой архитектуры, ко... Развернуть Свернуть
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/winal-Scree.git
git@api.gitlife.ru:oschina-mirror/winal-Scree.git
oschina-mirror
winal-Scree
winal-Scree
master