использование Синглтона для передачи учетных данных в мультитенантном приложении запах кода?


В настоящее время я работаю над мультитенантным приложением, которое использует подход Shared DB/Shared Schema. IOW, мы применяем сегрегацию данных арендатора, определяя столбец TenantID во всех таблицах. По соглашению, все операции чтения/записи SQL должны включать в себя , где TenantID = '?" пункт. Не идеальное решение, но ретроспектива-20/20.

В любом случае, поскольку практически каждая страница / рабочий процесс в нашем приложении должны отображать данные конкретного клиента, я принял (плохое) решение в начале проекта, чтобы использовать Синглтон для инкапсуляции учетные данные текущего пользователя (т. е. идентификатора и идентификатор пользователя). В то время я думал, что не хочу добавлять параметр TenantID к каждой сигнатуре метода в моем слое данных.

Вот как выглядит основной псевдокод:

public class UserIdentity
    {
        public UserIdentity(int tenantID, int userID)
        {
            TenantID = tenantID;
            UserID = userID;
        }

        public int TenantID { get; private set; }
        public int UserID { get; private set; }
    }

    public class AuthenticationModule : IHttpModule
    {

        public void Init(HttpApplication context)
        {
            context.AuthenticateRequest +=
                new EventHandler(context_AuthenticateRequest);
        }

        private void context_AuthenticateRequest(object sender, EventArgs e)
        {
            var userIdentity = _authenticationService.AuthenticateUser(sender);
            if (userIdentity == null)
            {
                //authentication failed, so redirect to login page, etc
            }
            else
            {
                //put the userIdentity into the HttpContext object so that
                //its only valid for the lifetime of a single request
                HttpContext.Current.Items["UserIdentity"] = userIdentity;
            }
        }
    }


    public static class CurrentUser
    {
        public static UserIdentity Instance
        {
            get { return HttpContext.Current.Items["UserIdentity"]; }
        }
    }

  public class WidgetRepository: IWidgetRepository{

    public IEnumerable<Widget> ListWidgets(){
         var tenantId = CurrentUser.Instance.TenantID;
         //call sproc with tenantId parameter
    }
  }
Как вы можете видеть, здесь есть несколько запахов кода. Это синглтон, так что это уже не юнит-тест дружественный. Кроме того, у вас есть очень тесная связь между CurrentUser и HttpContext объект. В расширенном смысле это также означает, что у меня есть ссылка на систему.Паутина в моем слое данных (содрогается). Я хочу погасить некоторые технические долги в этом спринте, избавившись от этого синглтона по причинам, упомянутым выше. У меня есть несколько мыслей о том, что может быть лучше, но если у кого-то есть какие-либо рекомендации или извлеченные уроки, которыми они могли бы поделиться, я был бы очень признателен.
1 2

1 ответ:

CurrentUser-это не совсем синглтон. Я не совсем уверен , Как бы вы это назвали. (Синглет по определению может существовать только один за раз, и любое количество экземпляров UserIdentity может быть создано по желанию внешним кодом и сосуществовать без каких-либо проблем.)

Лично я бы взял CurrentUser.Экземпляр и либо переместить его в UserIdentity.CurrentUser, или соедините его с любыми аналогичными методами и свойствами" get the global instance", которые у вас есть. Избавляется от текущего пользователя класс, по крайней мере. В то время как вы это делаете, сделайте свойство настраиваемым в том же месте-оно уже настраивается, просто таким образом, что (1) выглядело бы как волшебство, если бы два класса не были показаны рядом друг с другом, и (2) делает изменение способа установки текущего идентификатора пользователя позже сложнее.

Не избавляет от глобального, но вы действительно не сможете обойти это, не передав UserIdentity каждой функции, которая нуждается в нем.