Идея проекта в создании фреймоворка по удобству использования приближенного к динамическим языкам на языке со статической типизацией.
Вся легкость построения приложений на RoR обусловлена динамической генерацией множества хелперных методов, предлагается данные методы генерировать в компайл тайме nemerle макросами.
Идея принадлежит Воронкову Василию
соглашение вместо конфигурации, как в RoR, DRY.
имеет из коробки linq ORM (BlToolkit), view engine (Spark view engine), но имеет "разъемы" для замены их на другие.
как MVC движок предлагается использовать ASP.NET MVC
модель по базе utythbhetncz в компайл тайме, аналогично тому, как это делают рельсы в рантайме. Вот тут сыграет главная мощь nemerle, все свойства и методы будут контролироваться компилятором. Спарк кстати тоже умеет прекомпилировать вьюхи. Возможно использование обратного сценария, генерации схемы по модели, но я пока вижу тут очень много граблей.
версионность схемы СУБД поддерживаемых BlToolkit, a la RoR (вот тут пригодятся)
Плюсы по сравнению с RoR
компилируемость и скорость, прощайте тормознутые рельсы.
более продвинутый ORM, все таки в рельсах он убог ужасно
контроль компилятором всей "динамики", больше не нужно бояться сделать опечатку.
есть мысли, как использовать данный движок для десктопного УИ без переделок (http://www.rsdn.ru/forum/prj/3763379.1.aspx
нет кучи готовых компонентов, типа установи и получи нужный функционал, но это дело наживное
не умеет подхватывать изменения "на лету", хотя view это не коснется
и теряем типизацию, либо вынуждены объявлять класс для типизации, и типизировать view что действительно геморойно.
// модельclass ProductListModel
{
public IEnumerable<Product> Products {get;set;}
}
// контроллерvar model = new ProductListModel() { Products = db.Products.ByCategory(categoryId) };
return View(model); // кстати попробуйте сделать вьюху с моделью типа string
// view, это спарк, самый краткий синтаксис, в ASP.NET мы получим еще больший оверхед
<viewdata model="ProductListModel">
Это все противоречит принципам DRY, чтобы передать простой список изделий мы должны сбегать в кучу мест и узаконить это простое намерение.
Хотя по идее достаточно вывести тип того что мы хотим передать и сгенерировать данный DTO класс.
избавляемся от строковых констант с именами и сигнатурами экшенов, контроллеров
В идеале синатксис
Html.ActionLink(product.Name, "Show", new {id = product.Id})
Здравствуйте, Ziaw, Вы писали:
Z>Идея проекта в создании фреймоворка по удобству использования приближенного к динамическим языкам на языке со статической типизацией. Z>Вся легкость построения приложений на RoR обусловлена динамической генерацией множества хелперных методов, предлагается данные методы генерировать в компайл тайме nemerle макросами.
Идея в общем здравая. Но IMHO все сделать столь же удобно не получится.
Некоторые методы в RoR доопределяются только на этапе выполнения из принципиальной необходимости.
Пример:
ExtUser.find_by_sql("SELECT user.*, account.* FROM user INNER JOIN ... ну и так далее
Набор методов класса ExtUser будет известен только после выполнения запроса на основе полученных данных. А на этапе компиляции не известно нужно ли делать в этом классе метод usr.directory_name() или нет.
Это только пример, который первым пришел в голову. Реально подобных мест в RoR куча.
Здравствуйте, Alex EXO, Вы писали:
AE>Здравствуйте, Ziaw, Вы писали:
Z>>Идея проекта в создании фреймоворка по удобству использования приближенного к динамическим языкам на языке со статической типизацией. Z>>Вся легкость построения приложений на RoR обусловлена динамической генерацией множества хелперных методов, предлагается данные методы генерировать в компайл тайме nemerle макросами. AE>Идея в общем здравая. Но IMHO все сделать столь же удобно не получится. AE>Некоторые методы в RoR доопределяются только на этапе выполнения из принципиальной необходимости. AE>Пример: AE>ExtUser.find_by_sql("SELECT user.*, account.* FROM user INNER JOIN ... ну и так далее AE>Набор методов класса ExtUser будет известен только после выполнения запроса на основе полученных данных. А на этапе компиляции не известно нужно ли делать в этом классе метод usr.directory_name() или нет. AE>Это только пример, который первым пришел в голову. Реально подобных мест в RoR куча.
Это проблемы роровского ORM, linq выводит получаемый запросом тип, о нем все известно на этапе компиляции.
Здравствуйте, Ziaw, Вы писали: Z>Это проблемы роровского ORM, linq выводит получаемый запросом тип, о нем все известно на этапе компиляции.
Не-е-е...
Это не "проблемы роровского ORM". Это возможности "роровского ORM".
С реляционными данным я хочу работать на полноценном реляционном языке. И декоративный SQL выражает работу с данными очень компактно.
Но вот потом, я хочу чтобы то, что я "нагреб" превратилось в объекты. Чтобы мне удобнее было работать с этим в слое бизнес-логики.
И это, на мой взгляд, работа ORM (одна из). "Сделать объекты из того, что я выгреб".
А на тему объектного описания запросов... ну как там в поговорке: "не говорите мне что делать, и я не скажу куда вам идти".
В общем то это достаточно традиционный отмаз: "у нас это не реализовано потому, что это не нужно". Во флейме его использовать можно и даже успешно. Но и что с того? Реальное удобство фреймворка от этого не повысится.
Да, на rsdn модно ругать RoR-овский ORM. Но выбирают часто именно RoR.
Здравствуйте, Alex EXO, Вы писали:
AE>Это не "проблемы роровского ORM". Это возможности "роровского ORM". AE>С реляционными данным я хочу работать на полноценном реляционном языке. И декоративный SQL выражает работу с данными очень компактно.
С linq знакомы? Вполне полноценный реляционный язык.
AE>Но вот потом, я хочу чтобы то, что я "нагреб" превратилось в объекты. Чтобы мне удобнее было работать с этим в слое бизнес-логики. AE>И это, на мой взгляд, работа ORM (одна из). "Сделать объекты из того, что я выгреб".
Именно это он и делает.
AE>А на тему объектного описания запросов... ну как там в поговорке: "не говорите мне что делать, и я не скажу куда вам идти". AE>В общем то это достаточно традиционный отмаз: "у нас это не реализовано потому, что это не нужно". Во флейме его использовать можно и даже успешно. Но и что с того? Реальное удобство фреймворка от этого не повысится. AE>Да, на rsdn модно ругать RoR-овский ORM. Но выбирают часто именно RoR.
Здравствуйте, Alex EXO, Вы писали:
AE>Это не "проблемы роровского ORM". Это возможности "роровского ORM". AE>С реляционными данным я хочу работать на полноценном реляционном языке. И декоративный SQL выражает работу с данными очень компактно.
"Полноценный реляционный язык" SQL'92 за рядом тонких моментов вполне себе можно сделать статически типизированным. Так что и динамика при работе с ним не нужна.
AE>Но вот потом, я хочу чтобы то, что я "нагреб" превратилось в объекты.
А во что оно должно превратиться? И чем оно лучше объектов?
AE>А на тему объектного описания запросов... ну как там в поговорке: "не говорите мне что делать, и я не скажу куда вам идти".
А тебе тут никто и не говорит, что делать.
AE>Да, на rsdn модно ругать RoR-овский ORM. Но выбирают часто именно RoR.
Вопрос в том, по какой причине.
... << RSDN@Home 1.2.0 alpha 4 rev. 1469 on Windows 7 6.1.7600.0>>
Здравствуйте, AndrewVK, Вы писали: AVK>"Полноценный реляционный язык" SQL'92 за рядом тонких моментов вполне себе можно сделать статически типизированным. AVK>Так что и динамика при работе с ним не нужна.
По сути — да, можно. И на стороне ORM-а можно вычитать конфигурацию системных таблиц и настроить классы.
(Что собственно рельсовый ORM и делает.)
Но всегда остаются случаи "единичных запросов", под которые писать ручками расширение классов слишком геморойно.
И здесь я вижу только два пути:
— динамически расширять экземпляры, не трогая класс в целом;
— "невидимым" образом генерить класс-наследника с набором атрибутов адаптированных под этот конкретный запрос
(запрос для этого придется распарсить на этапе препроцессинга). Для того моего примера мог бы быть автоматически
сгенерен класс _ExtUserAccount наследник от ExtUser с дополнительными атрибутами.
Но на сколько я понял, в данном проекте речь о автоматической генерации классов на основе запросов не идет.
А тогда, для "единичных запросов" писанины станет куда больше.
Не, такой фрейворк тоже будет полезен (за быстродействие можно порой кой чем пожертвовать),
но я бы не стал питать иллюзий, что он будет столь же удобен, как и динамический.
AVK>Вопрос в том, по какой причине.
Твоя версия?
Здравствуйте, Alex EXO, Вы писали:
AE>Но всегда остаются случаи "единичных запросов", под которые писать ручками расширение классов слишком геморойно.
Какое такое расширение классов? О чем ты?
AE>И здесь я вижу только два пути: AE>- динамически расширять экземпляры, не трогая класс в целом; AE>- "невидимым" образом генерить класс-наследника с набором атрибутов адаптированных под этот конкретный запрос
О чем ты вообще?
AE>Но на сколько я понял, в данном проекте речь о автоматической генерации классов на основе запросов не идет.
Не нужно никаких классов под каждый запрос генерить.
AE>но я бы не стал питать иллюзий, что он будет столь же удобен, как и динамический.
Пока что ты питаешь какие то странные иллюзии по поводу linq.
... << RSDN@Home 1.2.0 alpha 4 rev. 1469 on Windows 7 6.1.7600.0>>
Здравствуйте, AndrewVK, Вы писали: AVK>О чем ты вообще?
Да, пожалуй разговор и правда не складывается. На разных языках говорим.
AVK>Пока что ты питаешь какие то странные иллюзии по поводу linq.
А разве я вообще о linq?
Я о возможности реализации некоторых удобных фич на статически типизированном языке.
В прочем — неважно.
Здравствуйте, Alex EXO, Вы писали:
AVK>>Пока что ты питаешь какие то странные иллюзии по поводу linq. AE>А разве я вообще о linq? AE>Я о возможности реализации некоторых удобных фич на статически типизированном языке.
Ты так и не ответил — зачем что то там генерить под каждый запрос.
... << RSDN@Home 1.2.0 alpha 4 rev. 1469 on Windows 7 6.1.7600.0>>
Здравствуйте, AndrewVK, Вы писали: AVK>Ты так и не ответил — зачем что то там генерить под каждый запрос.
Попробую. Просто если ты не пользовался экземплярными расширениями, то сложно объяснить на сколько это порой удобно.
Предположим у нас есть класс User с данными о пользователе и класс Account с данными о правах, тарифе и прочем...
Я хочу получить список пользователей с информацией о правах, причем получить его одним sql-запросом (для быстроедействия).
Если я хочу работать не в идеологии DataSet-ов, а с полноценными объектами User, то ORM должен обернуть результат запроса в эти объекты.
Но! У нас там "лишние атрибуты", котрых нет в классе User. Классическое решение в ORM на этот случай: вложенный объект user.account класса Account. А уж разложить результат запроса по нескольким связанным объектам любой нормальны ORM сможет.
Толко вот это все так, пока мы вытаскиваем все поля Account. А если это "тяжелый" объект, а нам нужно в данном случае только одно поле is_admin ?
Попытка создать связанный объект класса Account над одним полем, приведет к "неполноценному" объекту с непредсказуемым поведением (особенно если например в классе Account перекрыт геттер Account.is_admin()).
В общем то по условиям задачи нам и не требуется класс Account, а нужно лишь одно дополнительное свойство в класс User. Да только вот в статически типизированном языке его там не предусмотрено.
Рельсы-то поступят просто: расширят объекты — результаты запроса еще одним атрибутом. (Реально, там механизм хитее, но внешне выглядит именно так.) Если рассматривать ситуацию с точки зрения строгой типизации, то как бы возник еще один класс, наследник от User с дополнительным атрибутом is_admin.
При статической типизации такой класс видимо придется создать явно, в автоматическом режиме. Потому, что заставлять его писать пользователя — есть изврат. Остается конечно вариант работы на уровне нетипизованных DataSet-ов, но его нельзя назвать особо удобным решением.
Здравствуйте, Alex EXO, Вы писали:
AE>Если я хочу работать не в идеологии DataSet-ов, а с полноценными объектами User, то ORM должен обернуть результат запроса в эти объекты.
Совсем нет. Для построения ad hoc запросов в статически типизированных языках обычно используют кортежи. В шарпе для этого есть анонимные типы. Выглядит это так:
var userAndRights =
from u in DB.Users()
select new {u.Name, u.Account.IsAdmin};
foreach (var unr in userAndRights)
Console.WriteLine(unr.Name, unr.IsAdmin);
Все на 100% статически типизированно и не требует никакой генерации классов и не нужной динамики.
P.S. В С# 4 можно и динамику прикрутить. Но в данной конкретной задаче это кривое решение.
... << RSDN@Home 1.2.0 alpha 4 rev. 1469 on Windows 7 6.1.7600.0>>
Здравствуйте, AndrewVK, Вы писали: AVK>Совсем нет. Для построения ad hoc запросов в статически типизированных языках обычно используют кортежи. В шарпе для этого есть анонимные типы. Выглядит это так: AVK>[c#] AVK>var userAndRights = AVK> from u in DB.Users() AVK> select new {u.Name, u.Account.IsAdmin}; AVK>foreach (var unr in userAndRights) AVK> Console.WriteLine(unr.Name, unr.IsAdmin);
1. unr здесь будет какого типа? Просто кортеж? Или я могу к ней методы класса User применять? Что нибудь типа unr.ban(Time.new + 1.month)
2. для того, чтобы сравняться с RoR по удобству, нужно сократить код выше до одной строки.
Здравствуйте, Alex EXO, Вы писали:
AE>1. unr здесь будет какого типа? Просто кортеж? Или я могу к ней методы класса User применять? Что нибудь типа unr.ban(Time.new + 1.month)
Чтобы забанить пользователя не нужен сам объект, нужен только ключ. Поэтому никакого смысла делать ban методом класса User нет.
AE>2. для того, чтобы сравняться с RoR по удобству, нужно сократить код выше до одной строки.
foreach (var unr in DB.Users().Select(u => new {u.Name, u.Account.IsAdmin})
Console.WriteLine(unr.Name, unr.IsAdmin);
Здравствуйте, AndrewVK, Вы писали: AVK>Методы ORM класса? Не думаю, что это разумный дизайн.
А зачем мне видеть ORM классы вообще? Пусть об этом потроха фреймворка заботятся.
Нет, я хочу чтобы был создан класс прикладной модели. О чем и речь.
То есть думать только о двух вещах: структуре базы как таковой (для оптимизации запросов), и прикладной модели предметной области.
Промежуточные уровни торчать на виду не должны.
Здравствуйте, Ziaw, Вы писали: Z>Чтобы забанить пользователя не нужен сам объект, нужен только ключ. Поэтому никакого смысла делать ban методом класса User нет.
Так это ж пример был.
Реально может быть какой-нибудь документ, который нужно обработать (с прикрепленной к нему фамилией подписавшего).
Да мало ли еще что. Вопрос о "смысле" в нашем разговоре не стоит. Разговор только о "возможности".
А уж для чего эту возможность применить пользователи фреймворка найдут.
Здравствуйте, Alex EXO, Вы писали:
Z>>Чтобы забанить пользователя не нужен сам объект, нужен только ключ. Поэтому никакого смысла делать ban методом класса User нет. AE>Так это ж пример был. AE>Реально может быть какой-нибудь документ, который нужно обработать (с прикрепленной к нему фамилией подписавшего).
Какая проблема передать в метод обработки документ и фамилию подписавшего разными параметрами, не пытаясь засунуть их в один класс?
AE>Да мало ли еще что. Вопрос о "смысле" в нашем разговоре не стоит. Разговор только о "возможности". AE>А уж для чего эту возможность применить пользователи фреймворка найдут.
Какой интересный подход, пихайте все возможности которые придут вам в голову, даже если не видите в них смысла. Пользователи найдут им применение.
Вначале небольшой дисклаймер. Я не претендую на авторство идеи или какую-то руководящую роль в проекте. Я всего лишь хочу чтобы идея не умерла.
Попробую расписать этапы развития проекта, чтобы было понятно, что он реален и не требует заоблачных вложений.
Milestone 0. ASP.NET MVC 2 on Nemerle
Это начальная точка проекта, точка отсчета.
Milestone 1. Model.
Требуется написать макросы для генерации свойств и атрибутов bltoolkit'а в пустые классы модели помеченные макросом. Свойства генерируются по схеме одноименной с классом таблицы либо таблицы зданной как параметр макроса.
Перед началом работы нужно:
Дописать абстракции для работы со схемами СУБД поддерживающимыми тулкитом (это можно оформить как часть тулкита, если IT не будет против).
Продумать механизм конфига приложения, как минимум строк подключения.
Продумать правила сопоставления имен классов и имен таблиц для разных субд.
Этап считается завершенным в случае когда для работы с новой таблицей требуется только создать одноименный класс в проекте. Связи между таблицами считаю необязательными на этом этапе, возможно кто-то добрый доделает их параллельно.
Milestone 2. Migrations.
Если кто-то не знает что это — http://flux88.com/blog/net-database-migration-tool-roundup/.
На этом этапе требуется продумать и реализовать DSL для выполнения миграций в БД (это тоже можно оформить как часть тулкита, сами механизмы по изменению схемы и возможно поддержки версионности, а не DSL). Очень желательно, чтобы DSL умел автоматически строить undo хотя бы для простых ситуаций.
Этап считается завершенным если в шаблоне приложения можно легко создавать и гонять вверх/вниз миграции. Интеграцей в студию всего этого можно потом заниматься паралельно.
Milestone 3. Constants.
На этом этапе хочется уметь генерировать навигационные механизмы для указания View, Controller, Action, и некоторых стандартных урлов без применения магических строк.
Не хочется пока углубляться в детали, раннее видение таково:
Views.Home.Index или Views.Index просто возвращают строку View
Controller.Home.Index(par1, par2) должен возвращать какой-то экшен дескриптор для построения уролов.
Action.Index то же самое в конексте текущего контроллера.
Похожее делают в MvcContrib через T4.
Этап считается завершенным если все это генерируется без участия человека, всякие хтмл хелперы можно потом писать параллельно.
Milestone 5. Немного магии.
Здесь я хочу избавиться от классов viewmodel. Точнее сделать их невидимыми для программиста, их поля будут задаваться прямо в контролле макросами.
def model = Views.Home.Model();
model.par1 = 10;
model.par2 = "welcome to nemerle on rails world";
Не уверен пока, что это вообще возможно, но очень хочется.
Milestone 6. Шаблоны для студии, правка багов, замечаний.
Собственно сабж, подготовка к бэте. На этом этапе революционное развитие первой версии останавливается, начинается эволюция.