Единица объема работ - PullRequest
       25

Единица объема работ

2 голосов
/ 04 июня 2011

У меня есть решение, которое использует веб-формы для внешнего интерфейса и mvc для консоли администратора.

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

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

Я начал с идеи, что единица работы будет зафиксирована пользовательским интерфейсом в конце запроса в методе страницы, таком как событие нажатия кнопки. То же самое относится к контроллеру.

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

Вот пример того, как написаны мои слои ...

public class CourseService
{
    private readonly ICourseRepository _repo;

    public CourseService(ICourseRepository repo)
    {
        _repo = repo;
    }

    public IEnumerable<Course> FindCoursesBy(string searchTerm)
    {
        var courses = _repo.FindBy(searchTerm);
        var log = string.format("search for '{1}' returned {0} courses",courses.Count(),searchTerm);
        _repo.LogCourseSearch(log);
        //IMO the service layer should be calling Commit() on IUnitOfWork here...
        return courses;
    }
}

public class EFCourseRepository : ICourseRepository
{
    private readonly ObjectContext _context;

    public EFCourseRepository(IUnitOfWork unitOfWork)
    {
        _context = (ObjectContext)unitOfWork;
    }

    public IEnumerable<Course> FindBy(string text)
    {
        var qry = from c in _context.CreateObjectSet<tblCourse>()
            where c.CourseName.Contains(text)
            select new Course()
            {
                Id = c.CourseId,
                Name = c.CourseName
            };
        return qry.AsEnumerable();
    }

    public Course Register(string courseName)
    {
        var c = new tblCourse()
        {
            CourseName = courseName;
        };
        _context.AddObject(c);
        //the repository needs to call SaveChanges to get the primary key of the newly created entry in tblCourse...
        var createdCourse = new Course()
        {
            Id = c.CourseId,
            Name = c.CourseName;
        };
        return createdCourse;
    }
}

public class EFUnitOfWork : ObjectContext, IUnitOfWork
{
    public EFUnitOfWork(string connectionString) : base(connectionString)
    {}

    public void Commit()
    {
        SaveChanges();
    }

    public object Context
    {
        get { return this; }
    }
}

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

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

Можете ли вы увидеть какие-либо непосредственные проблемы с моим подходом?

1 Ответ

2 голосов
/ 04 июня 2011

Это все о «границе» вашей единицы работы. Какова граница вашей логической операции? Это UI - код позади / контроллер или уровень сервиса? Под границей я имею в виду, кто определяет, что такое единица работы? Является ли ответственность разработчика пользовательского интерфейса хореографией нескольких обращений к одной единице работы или ответственность разработчика по раскрытию операций службы, каждая из которых охватывает одну единицу работы? Эти вопросы должны дать вам немедленный ответ, где следует назвать Commit на единицу работы.

Если граница ваших логических операций определяется разработчиком пользовательского интерфейса, вы не сможете сделать это таким образом - никогда. Разработчик пользовательского интерфейса может внести некоторые незавершенные изменения до того, как он вызовет ваш метод, но вы будете молча фиксировать эти изменения, как только зарегистрируете поиск! В таком случае ваша операция Log должна использовать свой собственный контекст / единицу работы (и, кроме того, она должна выполняться за пределами текущей транзакции), что потребует отдельной конфигурации, создающей новый экземпляр UoW для каждого вызова. Если граница ваших логических операций находится в службе, вы не должны предоставлять единицу работы разработчику UI - он не должен иметь возможности взаимодействовать с вашим активным экземпляром UoW.

Что мне не нравится в вашей реализации, так это Register вызов Commit на единицу работы. Опять где граница? Является ли операция хранилища автономной единицей работы? В таком случае, почему у вас есть сервисный слой? Что произойдет, если вы захотите зарегистрировать несколько проклятий в одной единице работы или если вы хотите, чтобы регистрация курса была частью более крупной единицы работы? Сервисный уровень отвечает за вызов Commit. Все это, вероятно, исходит из того, что ваш репозиторий будет проецировать сущности в пользовательские типы / DTO - это выглядит как слишком большая ответственность за репозиторий и слишком большая сложность. Особенно, если вы можете использовать POCO (EFv4.x).

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

Наконец-то. Вы обеспокоены обязанностями разработчика пользовательского интерфейса - в то же время разработчик пользовательского интерфейса может быть обеспокоен вашей реализацией - что произойдет, если разработчик пользовательского интерфейса решит запустить несколько операций сервиса параллельно (контекст EF не является потокобезопасным, но у вас есть только один для обработки всего запроса)? Итак, все дело в общении между вами и разработчиком пользовательского интерфейса (или в очень хорошей документации).

...