This post is from the professional Laravel developer community, original link: learnku.com/laravel/t/3…

(If you just want to see solutions,Click here for a )

Laravel is by far the most popular PHP framework, with a clear directory structure and elegant syntax. Using the default directory structure provided by Laravel works fine for small to medium-sized projects, but when you have a large application with more than 50 models, the code base can be a bit suffocating.

Maintaining a large application is not easy, especially when the organization is out of order, and Laravel’s default structure certainly doesn’t help much in such a scenario.

First, let’s look at the Laravel default structure and its impact on large applications.

Laravel’s default application structure:

|- app/
   |- Console/
      |- Commands/
   |- Events/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Jobs/
   |- Listeners/
   |- Providers/
   |- User.php
|- database/
   |- factories/
   |- migrations/
   |- seeders/
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/
Copy the code

There’s nothing wrong with the structure. But when we work in large applications, we typically divide our business logic into repositories, converters, and so on… As follows:

|- app/
   |- Console/
      |- Commands/
   |- Events/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Jobs/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- database/
   |- factories/
   |- migrations/
   |- seeders
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/
Copy the code

This is clearly a well-structured Laravel project. Now, let’s look at the Models folder:

|- app/ |- Models/ |- User.php |- Role.php |- Permission.php |- Merchant.php |- Store.php |- Product.php |- Category.php  |- Tag.php |- Client.php |- Delivery.php |- Invoice.php |- Wallet.php |- Payment.php |- Report.phpCopy the code

We also need to handle services for all the business logic. There is also the Repositories, Transformers, Validators folders, which contain the same or more classes. However, working with a single Entity/Model requires browsing through different folders and files.

The problem is not browsing different folders, but maintaining communication between the code and the service.

Analyzing the code base, we found:

  • It’s a holistic application
  • It’s hard for developers to maintain
  • Low development efficiency (need to be connected all the time)
  • Scaling is a problem

The solution is obvious – microservices. Even if we use SOA (service-oriented Architecture), we still have to break down our overall application into smaller, independent parts to scale them separately.

Typically, separating services requires two simple steps:

  • Move the service’s subroutines (Models, Repositories, Transformers etc.) into a new, smaller PHP microservice application.
  • Reconfigure the service function call to redirect the target to the microservice (for example, create an HTTP request).

Now you need to find all the files associated with the service, and you may find that its model or repository has been used elsewhere in your code by bypassing the service. To summarize the possible problems:

  • There are many files to consider
  • The chances of making mistakes are high
  • jars
  • Sometimes you need to rethink domain logic
  • There are new programmers to worship

The last reason is important because it is difficult for new developers to master the entire application in a short time. However, the project manager won’t give him much time. This can lead to sloppy patching, incorrect code placement, and further confusion for the next new developer.

Fortunately, we already have a solution -HMVC. Breaking the entire application into smaller parts, each with its own files and folders (for example app/ folder), but HMVC adds more complexity, and also when we move specific modules into microservices. We still need to keep controllers, middleware, and so on in the main code base. In most cases, moving to microservices requires redefining routes and controllers. Therefore, we have to do extra work. So I’m not a big fan of this structure. Because, I just want to separate what I don’t have to separate. The composer is automatically loaded as follows:

|- auth/
   |- Exceptions/
   |- Http/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- merchant/
   |- Console/
   |- Events/
   |- Exceptions/
   |- Http/
   |- Jobs/
   |- Listeners/
   |- Models/
   |- Presenters/
   |- Providers/
   |- Repositories/
   |- Services/
   |- Transformers/
   |- Validators/
|- database/
   |- factories/
   |- migrations/
   |- seeders
|- config/
|- routes/
|- resources/
   |- assets/
   |- lang/
   |- views/
Copy the code

But HMVC adds more complexity, and also when we move specific modules into microservices. We still need to keep controllers, middleware, and so on in the main code base. In most cases, moving to microservices requires redefining routes and controllers, and we have to do extra work. However, I only want to separate what I have to separate.

Domain-driven design

This article is no longer about domain-driven design, but DDD is well described here by Developerul DeLaUnu.

For the purposes of this article, DDD (can) divide Laravel applications into four parts (or three… See) :

  • Application – usually includes controllers, middleware, and routing
  • Domain – usually contains business logic (models, repositories, transformers, policies, and so on)
  • Infrastructure – Usually has common services such as logging, E-mail, and so on
  • Interface – Usually contains views, languages, and assets

What happens if we construct the application this way and use namespaces?

|- app/
   |- Http/ (Application)
      |- Controllers/
      |- Middleware/
|- Domain/
   |- Models/
   |- Repositories/
   |- Presenters/
   |- Transformers/
   |- Validators/
   |- Services/
|- Infrastructure/
   |- Console/
   |- Exceptions/
   |- Providers/
   |- Events/
   |- Jobs/
   |- Listeners/
|- resources/ (Interface)
   |- assets/
   |- lang/
   |- views/
|- routes/
   |- api.php
   |- web.php
Copy the code

It is not possible to project into such folders, which means we only add a parent namespace.

The solution

As follows:

|- app/ |- Http/ |- Controllers/ |- Middleware/ |- Providers/ |- Account/ |- Console/ |- Exceptions/ |- Events/ |- Jobs/  |- Listeners/ |- Models/ |- User.php |- Role.php |- Permission.php |- Repositories/ |- Presenters/ |- Transformers/ |- Validators/ |- Auth.php |- Acl.php |- Merchant/ |- Payment/ |- Invoice/ |- resources/ |- routes/Copy the code

Auth.php and acl.php are internal service files app/Account/ folder. The controller will only access these two classes and call their functions. Other classes (outside the domain) will not access the remaining classes in the app/Account/ folder. The functionality in these services will only accept basic PHP data types, such as Array, String, INT, bool, and POPO (Plain Old PHP Object), but no instances of classes. Such as:

public function register(array $attr) {... } publicfunction login(array $credentials) {... } publicfunction logout() {... }Copy the code

Note that the register function accepts the array property, not the User object. Because another class that is calling the function should not know that the User model exists. This is the basic rule of the whole structure.

When we want to separate code

Our application started to get big and then we wanted to separate the Account domain into a microservice and turn it into an OAuth service.

So, we just move the following –

|- Account/
   |- Console/
   |- Exceptions/
   |- Events/
   |- Jobs/
   |- Listeners/
   |- Models/
      |- User.php
      |- Role.php
      |- Permission.php
   |- Repositories/
   |- Presenters/
   |- Transformers/
   |- Validators/
   |- Auth.php
   |- Acl.php
Copy the code

To a new Laravel (or Lumen) app –

|- app/ |- Http/ |- Controllers/ | - Middleware/ |- Account/ |- Events/ |- Jobs/ |- Listeners/ |- Models/ |- User.php |-  Role.php |- Permission.php |- Repositories/ |- Presenters/ |- Transformers/ |- Validators/ |- Auth.php |- Acl.php |- routes/ |- resources/Copy the code

Of course, we have to write code in the controller and route to make it the OAuth service we need.

But what do we need to change in the main code base?

We just need to keep the auth.php and acl.php service files and change the code in their functions to HTTP requests (or some other method of messaging) for the newly created microservice.

. publicfunction login(array $credentials) {
 // change the code here
}
...
Copy the code

The entire application will remain the same. The application structure is as follows –

|- app/
   |- Console/
   |- Exceptions/
   |- Http/
      |- Controllers/
      |- Middleware/
   |- Providers/
   |- Account/
      |- Auth.php
      |- Acl.php
   |- Merchant/
   |- Payment/
   |- Invoice/
|- resources/
|- routes/
Copy the code

With this little effort, you can move a portion of your code into a completely separate microservice. I can’t imagine any other way to move a piece of code to another microservice with fewer actions.

Weigh the

As I said before, everything requires trade-offs, and this solution is no exception. Here we have a migration problem! Because in the file structure above (prior to separation), all migration files were placed in the Database /migrations/ directory. But when we want to separate a domain name, we also need to identify those files and move them under the new domain name. This can be difficult because we don’t have any clear indication of which migrated files belong to which domain. We need to know how to place identifiers in the migration file.

This identifier can be a domain name prefix. For example, we could name the migration file XXXXXXXXx_create_account_users_table.php instead of XXXXXXXXx_create_users_table.php. If desired, we can also use the account_Users table name instead of users. I prefer to determine which tables need to be moved in the split. Separating the migration files can be a little frustrating, but if we use prefixes or other types of tags, the process becomes less painful.

I’m still experimenting with this migration structure and plan to build a Laravel package that will provide artisan commands to automatically generate files during the separation process. I’ll put a link to the package here when I’m done.