dongqiulei6805 2015-10-26 16:27
浏览 54

装饰图案模拟多层服务层

The code examples are in PHP but the question is language agnostic.

Situation

I'm trying to figure out the best way to separate a service layer into multiple well defined layers.

In the example below I'm uploading a base64 encoded user avatar and showing how it would go through the layers. I'm using the decorator pattern to simulate the layers.

Important: The data passed into each layer is usually changed in some way before it is passed into the next layer which is exactly what I'm looking for. The one thing I don't like about this is that in order to update an avatar you must first talk to the ValidatedProfile object instead of say a Profile object. Something about it seems weird but I could always have a Profile object which delegates calls to the ValidatedProfile.

The Layers

  1. Validation: This is where you validate the data. As in the example below it is where you check the format of the $avatar string and ensure it is a valid image resource. During the validation process entity objects and resources are often created which are then passed to the next layer.
  2. Verification: Perform checks such as verifying if a supplied ID is real. As in the example below this is where I check if the supplied user ID is actually the real ID of a user.
  3. Commander: Where the action to be performed happens. By the time this layer is reached the data is thought to be fully validated and verified and no further checks need to be done on it. The commander delegates the action to other services(usually entity services) and can also call other services do more actions.
  4. Entity: This layer works with actions to be performed on an entity and/or its relations.

ValidatedProfile

class ValidatedProfile 
{
    private $verifiedProfile;

    /**
     * @param string $avatar Example: data:image/png;base64,AAAFBfj42Pj4
     */
    public function updateAvatar($userId, $avatar)
    {
        $pattern = '/^data:image\/(png|jpeg|gif);base64,([a-zA-Z0-9=\+\/]+)$/';
        if (!preg_match($pattern, $avatar, $matches)) {
            // error
        }

        $type = $matches[1]; // Type of image
        $data = $matches[2]; // Base64 encoded image data

        $image = imagecreatefromstring(base64_decode($data));
        // Check if the image is valid etc...

        // Everything went okay
        $this->verifiedProfile->updateAvatar($userId, $image);
    }
}

VerifiedProfile

class VerifiedProfile
{
    private $profileCommander;

    public function updateAvatar($userId, $image)
    {
        $user = // get user from persistence
        if ($user === null) {
            // error
        }

        // User does exist 
        $this->profileCommander->updateAvatar($user, $image);
    }
}

ProfileCommander

class ProfileCommander
{
    private $userService;

    public function updateAvatar($user, $image)
    {
        $this->userService->updateAvatar($user, $image);

        // If any processes need to be run after an avatar is updated
        // you can invoke them here.
    }

UserService

class UserService
{
    private $persistence;

    public function updateAvatar($user, $image)
    {
        $fileName = // generate file name

        // Save the image to disk.

        $user->setAvatar($fileName);

        $this->persistence->persist($user);
        $this->persistence->flush($user);
    }
}

You could then have a Profile class like the following:

class Profile
{
    private $validatedProfile;

    public function updateAvatar($userId, $avatar)
    {
        return $this->validatedProfile->updateAvatar($userId, $avatar);
    }
}

That way you just talk to an instance of Profile rather than ValidatedProfile which makes more sense I think.

Are there better and more widely accepted ways to achieve what I'm trying to do here?

  • 写回答

1条回答 默认 最新

  • dongqian6484 2015-10-27 14:54
    关注

    I think you have too many layers. Two major objects should be enough for an operation like this. You need to validate the avatar input and persist it somehow.

    Since you need to validate the user id and it is tied to persistence somehow, you can delegate that to the UserService object.

    interface UserService {
    
        /**
         * @param string $userId
         * @param resource $imageResource
         */
        public function updateAvatar($userId, $imageResource);
    
        /**
         * @param string $userId
         * @return bool
         */
        public function isValidId($userId);
    }
    

    This checking for a valid user id should be a part of request validation. I would not make it a separate step like verification. So UserAvatarInput can handle that (validation implementation is just an example), and a little wrapper method to persist it all.

    class UserAvatarInput {
    
        /**
         * @var UserService
         */
        private $userService;
    
        /**
         * @var string 
         */
        private $userId;
    
        /**
         * @var resource
         */
        private $imageResource;
    
        public function __construct(array $data, UserService $service) {
            $this->userService = $service; //we need it for save method
            $errorMessages = [];
    
            if (!array_key_exists('image', $data)) {
                $errorMessages['image'] = 'Mandatory field.';
            } else {
                //validate and create image and set error if not good
                $this->imageResource = imagecreatefromstring($base64);
            }
    
            if (!array_key_exists('userId', $data)) {
                $errorMessages['userId'] = 'Mandatory field.';
            } else {
                if ($this->userService->isValidId($data['userId'])) {
                    $this->userId = $data['userId'];
                } else {
                    $errorMessages['userId'] = 'Invalid user id.';
                }
            }
    
            if (!empty($errorMessages)) {
                throw new InputException('Input Error', 0, null, $errorMessages);
            }
        }
    
        public function save() {
            $this->userService->updateAvatar($this->userId, $this->imageResource);
        }
    
    }
    

    I used an exception object to pass validation messages around.

    class InputException extends Exception {
    
        private $inputErrors;
    
        public function __construct($message, $code, $previous, $inputErrors) {
            parent::__construct($message, $code, $previous);
            $this->inputErrors = $inputErrors;
        }
    
        public function getErrors() {
            return $this->inputErrors;
        }
    
    }
    

    This is how a client would use it, for example:

    class UserCtrl {
    
        public function postAvatar() {
            try {
                $input = new AvatarInput($this->requestData(), new DbUserService());
                $input->save();
            } catch (InputException $exc) {
                return new JsonResponse($exc->getErrors(), 403);
            }
        }
    
    }
    
    评论

报告相同问题?

悬赏问题

  • ¥15 Vue3 大型图片数据拖动排序
  • ¥15 划分vlan后不通了
  • ¥15 GDI处理通道视频时总是带有白色锯齿
  • ¥20 用雷电模拟器安装百达屋apk一直闪退
  • ¥15 算能科技20240506咨询(拒绝大模型回答)
  • ¥15 自适应 AR 模型 参数估计Matlab程序
  • ¥100 角动量包络面如何用MATLAB绘制
  • ¥15 merge函数占用内存过大
  • ¥15 使用EMD去噪处理RML2016数据集时候的原理
  • ¥15 神经网络预测均方误差很小 但是图像上看着差别太大