How to better design my application - Service layer

Dear, first of all sorry for my bad English, it is not my native language.

To the point, I want to say that I have been working with Yii2 for a few years but I have never fully exploited its capabilities, I also consider myself a programmer in constant advance.

Until now I have had to develop small and medium applications, but today I find myself with a bigger project.

I have not left the mvc pattern at the moment because I feel very comfortable working like this.

I have seen several threads from past years on the topic of delegating certain application logic and business model to another layer of services, the truth is that I have never worked with something like this or in other languages, so I have no experience.

But I am presented with cases for example where I have to save contact details such as addresses, telephone numbers and emails for various entities. And that seems to me that repeating it in different models, for example: clients, students, teachers, is not something well developed. I am giving scenarios but I have several of these.

So I was looking a little bit about SOLID principles and about cohesion, coupling, etc. So I fell into these threads where they talk about Yii2 lacking a layer of services, to be able to host everything that has nothing to do with the data models of the database, such as calculating taxes, calculating other types of questions that make to the business model but not precisely to the data in the database of an active record model.

So I ask, what is the recommendation, what are the recommended good practices? Where can I see a tutorial, documentation, to continue on a good path in my application design? since the official documentation only talks about fat models, skinny drivers. But there is no information about what I am talking about.

Well thank you very much in advance, and I wish you all be well in the face of this global health problem.

I await your advice.

1 Like

Yii 2 is not lacking services. They are called “application components”.

1 Like

Alexander! It is a pleasure that you have answered me, I know you are a core developer and you are taking the project forward. I use Yii from the creator stage until he stepped aside in the project.

Honestly, it is difficult for me to abandon Yii2 for all the wide range of tools it has, but it is also true that I have not come across projects as big as they have been so far, which is why I lack a bit of collaboration, perhaps due to my inexperience in certain As regards Yii2, I see it as very comfortable and super light and safe, when it comes to Yii2, when these discussions are put together, why it lacks a service layer, and why putting all the business logic into models that in turn they are active record, I am left without experience coverage. I would or would greatly appreciate it if you can provide me with some clean information or tell me from which tutorial I can see something about how to scale my application a little beyond forms, validations, scenarios, helpers, widgets and more. Because I still don’t have a very clear concept in Yii2 of injection of dependencies through components or of using components to carry out certain business logics that can be solved and used in the controller.

Of the type of things like the ones I mention, for example in the dynamic form extension of wbraganca, it does a lot of work in the controller and it seems to me that all that logic can be put in a place where it is only quoted in it without having to thicken its size. It is an opinion of a programmer with intermediate experience mine.

For example all this:

    public function actionCreate()
    {
        $modelCustomer = new Customer;
        $modelsAddress = [new Address];
        if ($modelCustomer->load(Yii::$app->request->post())) {

            $modelsAddress = Model::createMultiple(Address::classname());
            Model::loadMultiple($modelsAddress, Yii::$app->request->post());

            // ajax validation
            if (Yii::$app->request->isAjax) {
                Yii::$app->response->format = Response::FORMAT_JSON;
                return ArrayHelper::merge(
                    ActiveForm::validateMultiple($modelsAddress),
                    ActiveForm::validate($modelCustomer)
                );
            }

            // validate all models
            $valid = $modelCustomer->validate();
            $valid = Model::validateMultiple($modelsAddress) && $valid;
            
            if ($valid) {
                $transaction = \Yii::$app->db->beginTransaction();
                try {
                    if ($flag = $modelCustomer->save(false)) {
                        foreach ($modelsAddress as $modelAddress) {
                            $modelAddress->customer_id = $modelCustomer->id;
                            if (! ($flag = $modelAddress->save(false))) {
                                $transaction->rollBack();
                                break;
                            }
                        }
                    }
                    if ($flag) {
                        $transaction->commit();
                        return $this->redirect(['view', 'id' => $modelCustomer->id]);
                    }
                } catch (Exception $e) {
                    $transaction->rollBack();
                }
            }
        }

        return $this->render('create', [
            'modelCustomer' => $modelCustomer,
            'modelsAddress' => (empty($modelsAddress)) ? [new Address] : $modelsAddress
        ]);
    }

I do not know if it is correct to call a transaction from the controller or if it is convenient to put it in a class that handles this. I fall very fast in these existential doubts that block me in my daily work, because I don’t want to fall into a bad design. Also I have an entity for example physical person where I keep addresses, emails and phones, then I have another entity where I keep the details of an invoice, which is why I use multiple models in a single action.

On the other hand I tell you that I am from Argentina and on these sides Laravel is very fashionable and I do not find much market with Yii2, even so he has me in love and I would love him to become popular in the Hispanic community, so if I could collaborate in something I am willing.

Sorry for the long message but since you have responded in person you have motivated me a lot.

Greetings and stay well!

1 Like

Well I hope you can give me a little light on the subject. Greetings again!

In your code snippet there are:

  1. Dealing with request and response (rendering a view). That definitely belongs to controller.
  2. Validation. That’s OK to call it in controller.
  3. Saving data including transactions. That may be extracted into repository such as CustomerReponsitory::saveWithAddresses(Customer $customer, array $addresses).
  4. Redirection on success. That is controller job, it’s fine.
1 Like

I understand that said repository class can be the same ActiveRecord model? my way of working is first to create the migrations well to establish the database with its relations and normalization to the normal n3 form if possible. Then I create all my models with gii and thus I obtain the validations that I already pre-configure in the database.

So my model classes are all extended from Active Record. Can this be my repository class? I have not worked with the repository pattern until now.

On the other hand, if this saveWithAddressess method that you suggest, is repeated both in clients and organizations and in other models, as can happen with an image upload method, for example uploadImages (Customer $ customer, array $ images), as it should Make my design so as not to repeat myself in the code, I know I am asking you a bit more about programming logic but at the moment I am working on how to do it on Yii2.

Thank you very much again. Regards!

Repository class may use ActiveRecord model inside but should not be active record itself because it would know too much about other records.

Repositories are classes meant to load/save data for specific use-cases i.e. PostRepository may have methods such as save() and getForFrontPage(), getForArchive().

Trying not to repeat yourself too much leads to disaster. If everything in your project has the same piece of code reused for saving with addresses and then one use-case needs something special you either have to make this special case use special method or adjust general method to include that case. First variant is just a bit more work while second variant has a high chance of breaking everything during adding a special case.

1 Like

Ah I understand, I can have for example an ActiveRecord Customer class, where is the model of the entity of the database, and then it will be in charge of knowing about the data of each record. On the other hand, I can create a CustomerRepository class so that it is understood that it has a relationship to the same entity, which I can extend or not from ActiveRecord if I wanted to have the save () method, and there host methods and properties that help me separate the business logic. I can create this inside a folder called repositories. Am I getting it right? Do you know of any place where I can read or feed on information so as not to disturb the forums on this subject Alexander? I wish I could master these conceptual issues well and advance to a higher level of my experience.

Thank you very much again for the exchange.

Regards!

Repsitory is a layer that knows how to save entities but doesn’t expose how it is doing that so it’s possible to adjust the process of saving without breaking things (save to DB and search engine, save using plain SQL, save to cache etc.). It is still not business logic but helps things break less. As for directory, yes repositories would do.

Do disturb forums. Their purpose is exactly that :slight_smile: Since in Yii 2 many things have different names and architecture isn’t exactly standard, some adjustments are needed during explanation. Otherwise I’d recommend Matthias Noback book on objects. Yii 3, that is in development but already works, is more aligned with traditional naming and architecture but still, Yii 2 is a good framework.

1 Like

Slightly off-topic, but

It’s a very good advice from an experienced developer.

We sometimes make things unnecessarily complicated and fragile by seeking too much DRY. You have to learn to be happy keeping things BORING simple and easy. :stuck_out_tongue_winking_eye:

1 Like

Dear Alexander, it is very good to have your advice, unfortunately studying English is my goal this year so I cannot understand that book, I must look for something in Spanish at the moment, but I understand what you are saying and I will try to apply it. I love programming and I love Yii2, we will see Yii3 that brings new when it comes to implementing it in a new project.

Regarding not repeating so much and wanting to apply that term, sometimes it is true that everything ends up being more complicated. But hey, it’s the experience that makes you safe.

I hope some time there is a good Spanish-speaking community as I mentioned above, it is a sin that programmers miss out on a tool as powerful and extensible as Yii.

Regards!!

Hello dear softdark!

If the DRY principle like many others, SOLID, etc. Sometimes they lead to wanting to do everything according to rules and slow down developments, especially when the teams are not large.

But my doubts began when I started to say, to feel ugly smells in my models and that in the controllers I used things that should not go there. Then, as restless as I am, I began to investigate and found a world of dilemmas. What I’m trying to do is reduce those dilemmas and adjust to what the framework gives me. That is why I started this as a service layer without even knowing or having used it, I know it is something more than JAVA and spring but then I wanted to know how to undock my application a little. Anyway, things work, but hey, the idea is that it is maintainable and scalable and does not end up being a spaghetti.

Although I have learned a lot, I still have a lot to learn, and as I say to Alexander, I would love for Yii to grow in Latin America and be able to position himself, although I am not an expert, I offer my collaboration in whatever is necessary for this to happen.

Greetings and thank you very much!

1 Like

Ah, I forgot to comment, regarding many search methods I have included them in the Search model, for example when I do the CRUD of an entity with gii, in the ClientSearch model, I create some custom methods with the necessary joins to obtain complex queries, I pass those directly to the controller and then to view with a dataProvider, so I have a lot of rich cross information for the user. So that’s why I had never felt the need to create repositories or service layers (I mentioned them the wrong way, but that’s how I started the post).

So it is still not clear to me if the repository class should be extended from ActiveRecord or not, since if I want to make a method to save in the database, it would be much easier for me to do save () than a query with queryBuilder or createCommand. But at the same time the question arises whether I should directly include the model in the constructor to work, or I can manage the dependency with the model in another way.

Going back to the Client example. If I have Client (ActiveRecord) and then create ClientRepository, how are they related to each other? and as I must quote them in the controller, that is my existential doubt.

Thank you!

No, it should not be extended from ActiveRecord.

Fine, do it. But inside of repository.

Ideally data transfer object or entity should be used but passing AR to the method would do for majority of cases.

1 Like

Alexander, thank you very much again and sorry for extending the thread but I am understanding much more, unfortunately this information I have not understood with the documentation, just at this time we are talking I am reviewing the creation of components.

Let’s see if I finish understanding this and don’t bother anymore.

If I don’t extend the repository from ActiveRecord how do I use the save () method …

I don’t understand the concept of data transfer, it is obvious that I need to raise my level. I will look for information in Spanish if possible.

I appreciate this thread, go ahead with this fabulous frame!

Regards!

In its simplest form:

class PostRepository
{
    public function save(Post $post): Post
    {
        $post->save();
        return $post;
    }
}

Or if you want to totally hide active record:

class PostData
{
    public $id;
    public $title;
    public $content;
}

class PostRepository
{
    public function save(PostData $data)
    {
        if ($data->id !== null) {
            $post = Post::find()->where(['id' => $data['id']])->one();
        } else {
            $post = new Post();
        }
        $post->load($data);
        $post->save();
        // likely you'll need to return errors here
    }
}
1 Like

Hello Alexander! Now I understand then. In this first case, we should pass the active registry through dependency injection, so it would have all its methods available, is that so? Perfect.

For this second case or example instead of passing it through dependency injection you should have in the beginning the famous one: use app \ models \ Post; in order to use the find () method, is this so?

Finally two more points since I find this conversation very enriching:

  1. Can I quote this from the controller? I mean I can use these methods directly on it and thus get the returned values.

  2. I see that you use php 7 syntax for method signing, is this mandatory? because I’m just getting used to using php as a strongly typed language.

Greetings and thank you very much again, from the other end of the planet :wink:

Yes. Repository could be put into dependency injection / service locator.

Yes, if you’re using a class you should import it with use.

From controller it will be:

public function actionEdit($id)
{
    // it is better to obtain it from DI container, keeping it simple for example
    $postRepository = new PostRepository(); 
    $post = $postRepository->findById($id);
    if ($post === null) {
        // 404
    }

    // do the form part, obtain data to be saved

    $postRepository->save($post);
}

No, it is not mandatory. Just my new habit.

1 Like

Well Alexander! I think I have already taken a lot of valuable time out of your life. The DI Container is a topic that I am reading https://www.yiiframework.com/doc/guide/2.0/es/concept-di-container, it is also new to me and although it seems easy to apply, I do not understand the difference between doing it one way or another. I mean I don’t know why and when I would apply it, these are questions that I have to practice again.

I really appreciate your time and I think my initial consultation is much more than answered.

I hope that this thread will serve someone else since it is as you previously mentioned the spirit of a forum.

Best regards! Go ahead with Yii!

I was thinking Alexander, in addition to the official documentation of Yii2 about the dependency injection container, which is very good, for the intermediates like me, there is a tutorial even if it is in English and another language where that theory is applied to a actual use case? It is hard for me to understand where a DI container should be configured and in which cases it may be necessary and efficient.

Thank you very much and again sorry.