web-dev-qa-db-ja.com

ログインサインアップのアーキテクチャ/デザインパターン

Opencart 2. から派生したマルチセラーeコマースプラットフォームを持っています。 Opencartのコアコードのかなりの部分を変更して、セラーダッシュボードなどの独自のカスタマイズを追加しました(セラーは注文を確認したり、請求書を作成したりできます)。 コードの繰り返しと、Opencartでの抽象化のような基本的なOOP原則の欠如 については、かなり詳しく説明されています。

今、私たちはスケールアップするので、それを書き直しています。現在、ログイン/サインアップモジュールから始めています。顧客または販売者は、同じフォームを使用してログイン/サインアップできます。ログインにはCookieベースのトークン検証を使用します。また、お客様のデバイス/ブラウザ/ Androidアプリごとに異なるトークンを維持することで、彼は1か所でログインしたまま、別の場所からログアウトできるようにします。顧客/販売者がログインしているかどうかに応じて、適切なページにリダイレクトされます。

私は次のクラス構造を考えています:

Login Class {
  public login();
  public logout();
  public validate();
}
Signup Class extends Login Class {
  public login();
  public logout();
  public validate();
  public signup();
}

ログインコントローラは、Signupクラスのオブジェクトを作成し、login/signup/validateメソッドを呼び出します。ログイン/検証結果に応じて、適切なオブジェクト(顧客または販売者)を返します。

私は正しい線で考えていますか?これを達成するための設計パターンはありますか?

質問が広すぎる、または複雑すぎるようです。親切に私に知らせてください。私のケースの具体的な詳細をさらに詳しく説明させていただきます。前もって感謝します。

1
Madhur Bhaiya

質問からの考察およびコメント

タイトカップリング:

あなたは述べました:

(1)ログインコントローラは、サインアップクラスのオブジェクトを作成します
(2)顧客または販売者は、同じフォームを使用してログイン/サインアップできます。

(1)システムの2つの部分が互いに属さない方法で密結合している。

(1)また、あなたが持っているコード例と矛盾します:

Signup Class extends Login Class 

2つは反対のアプローチなので。

SignupでLoginを一緒に使用していて、SignupとLoginがエンドユーザーにいくつかの共有された概念を持っているという点で実際の類似点があるため、(2)に従ってそれらが関連/結合されている理由についての考え方を理解できます。また、サインアップする前に、ログインしていないことを確認する必要があります。

ログインしていないことを確認するために、これはすべてのページで発生する可能性があるため、すでに知っています。しかし、そうでない場合、Signupコントローラーは、ログインしているかどうかを確認するために別のサービスを呼び出します。この他のサービスはさまざまなページで再利用されるため、1つのクラスで「ログイン」するものを一度定義すると、 -コード全体で使用されます。

クラスには常に依存関係があり、他のオブジェクトが(たとえば [〜#〜] di [〜#〜] を介して)持ち込まれますが、この場合、システムではこれらのcontrollers =は直接関連していません。

オブジェクトをクラスに取り込むことは1つのことであり、多くの場合、何らかの形の結合があります。クラスにはシステムの他の部分からの依存関係があります。そうでない場合、 [〜#〜] srp [〜#〜] に準拠することができません。しかし、「拡張」はvery密結合の関係であり、開発者が行うべき最初の呼び出しのようなものと見なすべきではありませんnless非常に正当な理由があり、その後、それは通常非常に明白です。

クラスは1つのクラスしか拡張できません。サインアップは「ログインクラス」でその特権を使用しました。しかし、Signupクラスは、Signupフォームデータを検証するための検証クラスを必要としませんか?そして、ユーザー名が使用されていないかどうかを判断するためのデータベースクラス(たとえば、リポジトリを呼び出すサービス)?等。

では、なぜデータベースクラスの代わりにログインクラスを拡張するのでしょうか。答えは、「どちらを拡張するかを選択する方が重要」ではなく、「これを結合するかどうか」です。これらのどれも拡張しない可能性が高いので、 composition を使用します。

良いが 構成の簡単な例
も参照してください 継承の構成

あなたの声明について(1)-サインアップする前にログインすることはできないので、LoginはSignupにパーティーに来るように要求するビジネスはありません。

「SignupがLoginオブジェクトを取得する」という別の方法(コード例)を記述する方が間違いなく論理的ですが、実際には、Signupでさえ、Signupが完了したときでもLoginにパーティーへの参加を要求するビジネスはありません。これは、これらがシステムの別々の部分であり、Signupがそれ自体を処理するためにneedログインしないためです 単一の責任 -Signup!

要件は「サインアップ」と「ログイン」に分かれているため、これらの特定の要件を組み合わせるのは「サインアップ後にログイン」であり、「サインアップを使用して最終的にログインできるようにする」ではありません。

つまり、ログインとサインアップは、ログインフォームとユーザー資格情報の検証に同じコードを使用するなど、システムからの共通の基盤を共有する必要があります。これらは、再利用のために共有する必要があるためです [〜#〜] dry [〜#〜] 。ただし、このような共通の根拠は、制御クラスをこのようにまとめる(1)か、他のオブジェクトのインスタンスを組み込む必要があるという意味ではありません。

場合によっては、ある種のメディエーターが理にかなっていて、共有された要件とオブジェクト(サービスなど)をまとめることができますが、この場合は、システムの次の部分にhandoverします。サインアップ後にユーザーをログインします。

別のわずかにstretchedの例で、上記の点を繰り返します。

ログイン後にそれらをUserProfileページに送信するとします。UserLoginオブジェクトをUserProfilePageControllerに入れないでください。ログインが完了すると、システムはすでにプロファイルコントローラにあり、準備ができています。

ログインシステムが呼び出され、ログインコントローラは、要求されたリソース(ログインの前にログインにリダイレクトするページ、または単にユーザープロファイルページなど)に渡します(これは可能性が高いですが)その他、これを制御する、フロントコントローラーまたは内部配線など)


密結合はとても悪いですか?

(ステートメント1)とこれらを密接に結合することの問題は、単に誰かを(単に数か月前にサインアップした)ログインするだけの場合に、必要のないときにログインがSignupオブジェクトを引き続きもたらすことです。

または、さらに悪いことに/まったく同じように、Signupに関連付けられていない純粋なLoginのためだけに2番目のLoginクラスを作成する必要があります。次に、違反しました [〜#〜] dry [〜#〜] 2つのLoginクラスは同じことをするため、重複して(間違って)カップリングの悪い設計に対応するだけです事。

もう1つの問題は、ログインまたはサインアップをリファクタリングするときに、お互いのオブジェクトに密接に結合しているため、もう1つを考慮する必要があることです。

もちろん、そのような密結合は有効であり、依存関係はクラス間で非常に共有されます。重要なのは、可能な限り分離と分離を適切に行うことです。これは、システムの一部が互いに独立して動作し、システムの一部にのみ影響を与える変更を意味します。


おそらくより良いアプローチ:

登録リクエスト

  1. SignupFormLoginFormをリクエストして表示する
  2. 入力したデータをSignupFormValidationLoginFormValidationに渡す
  3. エラーが表示された場合[1に戻る]
  4. それらを登録する
  5. ログインシステムに引き渡します(ただし、システムはこれを行いますが、SignupクラスにLoginのインスタンス、おそらくメディエーターサービスを持たせることではありません)。

ログインリクエスト

  1. リクエストして表示LoginForm
  2. 入力したデータをLoginFormValidationに渡します
  3. エラーが表示された場合[1に戻る]
  4. ログインする

サインアップとログインの両方が同じようにLoginFormLoginFormValidationを呼び出す方法を確認してください。これは、たとえば、ユーザー名を18文字ではなく20文字にできると決定した場合、中央の場所(LoginFormおよびLoginFormValidation)で変更を行い、SignupとLoginの両方が同じ変更を継承することを意味します。

彼らがサインアップしたときにログインするために、サービスなどのある種のメディエーターを提案します。おそらくLoginAfterSignupServiceのようなものと呼ばれます(より良いアプローチがあるかもしれないので、これは議論の余地があります)。

サインアップは、ユーザー名とパスワードをこのサービスに渡します。このサービスの仕事は、ログインシステムに渡すことです。これは、ユーザー名/パスワード(など)をログインシステムに渡します。ログインシステムは、通常のことを行い、ユーザー名とパスワードを確認してログインします。

ログインシステムは、ユーザーを確認するためにユーザー名とパスワードを常に必要とします。これは、ログインフォームからのものか、サインアップシステムが呼び出すメディエーターサービスからのものかは関係ありません。ログインシステムはwhereを気にする必要はありません。

サインアップの代わりにメディエーターサービスを提案してログインシステムに引き渡す理由は、ログインページではなく、サインアップではなく、中間。そのため、ログインに失敗した場合、別のサービスを利用していて、「サインアップ後にログイン」という中途半端な点を処理するだけで、ログインがfalse(失敗)を返した場合に特定のチェックを実行できます。

たとえば、ログインページには「申し訳ありませんが、ユーザー名またはパスワードが間違っています」と表示されますが、サインアップしただけで、そのメッセージはユーザーを混乱させます。代わりに、特定のサービスがそれをより適切に処理できます(適切なものはシステムによって異なります)。


質問のコードに関する簡単なメモ:

申し訳ありませんが、これは悪臭です:

Login Class {
  public login();
  public logout();

「ログイン」クラスにメソッド「ログアウト」を含めることはできません。これを使用するコードは扱いにくく見えます:

$login = new Login();
$logout = $login->logout();

// Method chaining would be worse
$logout = new Login()->logout();

別のログアウトクラスを作成して、次のようにします。

$logout = new Logout();
$logout = $logout->logout();

// Method chaining would be Nice to use
$logout = new Logout()->logout();

現在または将来、ログアウトプロセスが変更されたり、メールを送信したり、何か特定のもの(など)を確認したりすることがあります。これがクラスの場合、これらの新しい要件はそのクラスで有効なメソッドです。 「logout」が「login」クラスのメソッドである場合、 [〜#〜] srp [〜#〜] の違反である場合を除き、あらゆる種類の「logout」要件を満たす必要があります「ログイン」クラスの単一の「ログアウト」メソッドに。


これは非常に大雑把に見えると思います

class SignUp
{
    public function __construct(
        IsUserLoggedIn $isUserLoggedIn,
        LogUserInAfterSignupService $logUserInAfterSignup,
        RegisterNewUserService $registerNewUser
    ) {
        $this->isUserLoggedIn = $isUserLoggedIn;
        $this->logUserIn = $logUserInAfterSignup;
        $this->registerNewUserService = $registerNewUser;
    }

    public function signupAction(
        SignupForm $signupForm,
        SignupValidation $signupValidation,
        LoginForm $loginForm,
        LoginValidation $loginValidation
    ) {
        if ($this->isUserLoggedIn() === true) {
            // Probably show a view page telling them they're already logged in
        }

        // If no form data, pass to view the $signupForm and $loginForm

        // If form data
          // Pass relevant data to $signupValidation and $loginValidation
          // Show errors if exist

        // If form data and no errors
          $userId = $this->registerNewUser($formData);
          $this->logUserIn($userId);

        // Redirect to wherever

    }

    private function isUserLoggedIn()
    {
        return $this->isUserLoggedIn->userLoggedIn();
    }

    private function registerNewUser($formData)
    {
        // Call a service that calls a repository that insert form data (etc)
       // registerNewUserService would return the new user ID
        return $this->registerNewUserService->registerNewUser($formData);
    }

    private function logUserIn($userId)
    {
        $this->logUserIn->logInUser($userId);
    }
}

あなたはそこにいくつかの健全性チェックを望み、おそらくいくつかの例外をスローします(または、RegisterUserServiceやLogUserInAfterSignupServiceのように、このクラスが呼び出すサービスがスローする可能性があります)。

1
James