将来を見据えたウェブ・アプリケーションの構築:The Codestのエキスパート・チームによる洞察
The Codestが、最先端技術を駆使してスケーラブルでインタラクティブなウェブアプリケーションを作成し、あらゆるプラットフォームでシームレスなユーザー体験を提供することにどのように秀でているかをご覧ください。The Codestの専門知識がどのようにデジタルトランスフォーメーションとビジネス...
In the second episode of our Ruby on Rails modularization with Packwerk we will take a close look at the concept of application as an package.
The approach to modularize our application consists in converting the entire application into a package.
First, we need to create app/packages
folder where we will place all our packages. In order to isolate our packages we have to separate every MVC concept in one folder. Taking the コードトリアージ プロジェクト as an example we will have something like the following picture.
If we try to run the server, it will fail to find the constants. That’s why we need to add a line of configuration to our application.rb
config.paths.add 'app/packages', glob: '*/{*,*/concerns}', eager_load:true
Now the application works but it cannot find the views, so we need to add another line of configuration to our application_controller.rb
append_view_path(Dir.glob(Rails.root.join('app/packages/*/views')))
Our structure is ready, so now we can start creating the packages. In order to do that, we only need to add apackage.yml
to every folder with the following configuration:
enforce_privacy: false
enforce_dependencies: true
enforce_privacy
gives us the possibility to isolate all the constants of the package and work with a public API. In order to expose the public constants, we need to add the constants in, for example packages/users/app/public.
For now we are going to set this configuration to false.
enforce_dependencies
will enforce the dependency of a package and check for all the constant references. If a dependency is not explicitly defined it will be a violation of the boundary.
パックワーク established a criterion we need to follow in order to have a valid package system. We can start running packwerk validate
in our console.
This will check our folder structure, package configuration, and autoload path cache.
Right now, our application is not valid and we have to fix the load paths inpackwerk.yml
. In order to do this, we only have to add the missing paths.
# packwerk.yml
load_paths:
.
.
.
# Users
- app/packages/users/controllers
- app/packages/users/models
- app/packages/users/package.yml
- app/packages/users/views
At this point, we are ready to check boundary violations in our application. To check violations we can runpackwerk update-deprecations
, this command will generate deprecated_references.yml
file for every package. In every file, we will find package name, type of violation, and file path. With all this information we know where the violation is happening and we can make a decision to resolve it.
# deprecated_references.yml
.
.
.
app/packages/repos:
"::Repo":
violations:
- dependency
files:
- app/packages/users/models/user.rb
Taking the example we are going to describe every part of the information generated
によって パックワーク.
– app/packages/repos
– package where the constant violation is
found.
– ::Repo
– path to the file containing the violated constant.
– dependency
– a type of violation, either dependency or privacy.
– app/packages/users/models/user.rb
– path to the file containing the violated constant.
As a final step in this section, don’t forget to add the new generated file paths to packwerk.ym
l and run validations again.
With all the information in package.yml and deprecated_references.yml
we can then
visualize a graph of dependencies. In order to do that we need to add another gem, in this case we will use Pocky.
Running rake pocky:generate
we will generate a file called packwerk.png
where we can visualize our first graph of dependencies.
With all the packages defined our graph will look like this.
dependencies already exist but it doesn’t mean they are accepted by パックワーク. To
accept a dependency we need to add dependencies configuration to the package.yml
in every package. We will focus on mail_builders
since it’s a package without circular dependency. It’s worth mentioning that パックワーク won’t let us accept circular dependencies.
# app/packages/mail_builders/package.yml
```ruby
enforce_privacy: false
enforce_dependencies: true
dependencies:
- app/packages/docs
- app/packages/issues
- app/packages/repos
After adding this configuration, Pocky will color the accepted dependencies using green.
We can delete deprecated_references.yml
より app/packages/mail_builders
and runpackwerk update-deprecations
again. The file won’t be generated again since all the
violations were fixed for this package. It’s important to mention that even if we don’t Graph with accepted dependencies
Ruby on Rails modularization with Packwerk accept dependencies our application will still work as before, but now we have more
information to take decisions and refactor.
In our previous graph, we had a lot of circular dependencies that needed to be resolved somehow. We have different strategies to do that:
– Do nothing,
– Accept dependencies, Merge packages,
– Move コード between packages,
– Duplicate a functionality,
– Perform dependency injection or Dependency injection with typing.
One issue here is that in order to do a proper refactor, we need to know the codebase. I’m not so familiar with the codebase of this project since I took it as an example, so for practical reasons we will go with the first strategy, do nothing. Even if we will avoid most of the refactoring, we want to work on the dependencies in the ルート パッケージで提供される。
The root package contains all the glue from the Railsフレームワーク, all the classes we inherit from and make all work together. So, in order to solve the circular dependencies, we are going to create a new package called rails within the following steps:
app/packages/rails
.package.yml
for the package with the same configuration as the previous packages.packwerk.yml
.app/packages/rails
as a dependency from the rest of the packages.Once we create the package we will start to notice a lot of files that can be re-structured. After moving everything to the corresponding package and accepting
dependencies we will have a new structure and a cleaner graph.
Now our graph looks much better would be great if we can remove all the dependencies from root package. If we check deprecated_references.yml in the root package, we will notice that most of them are from テスト
, lib/tasks
, db
そして config
folder. In order to resolve these dependencies, we are going to create a test folder within every package. Having something like app/packages/users/test
. Next, we are going to exclude lib/tasks
, db
そして config
among other folders from パックワーク analysis since those dependencies aren’t really important in our analysis and we don’t have an easy way to resolve them. We will add the following to our packwerk.yml.
exclude:
- "{bin,node_modules,script,tmp,vendor,lib,db,config,perf_scripts}/**/*"
- "lib/tasks/**/*.rake"
After moving all the tests from root package and excluding the folders from the analysis we will have a new graph without root dependencies.
As we can see, we still have circular dependencies inユーザー
, repos
そして 諸注意
. Although we didn’t resolve them, we have important information to pass now. We know that every チーム that performs changes in one of those packages will probably have to perform changes in the packages with the circular dependency. On the other hand, we know that a team can work on github_fetchers
solely, knowing what packages are
getting affected with the changes in every moment.
You can find the final result of the project これ.
As a next step, you could enforce constant privacy in every package and expose only the public API that will be accessible from other packages. You can easily configure where your API will be placed in package.yml.
enforce_privacy: true
enforce_dependencies: true
public_path: my/custom/path/
パックワーク gives us a lot of information about our application and with that information we can take decisions to improve the workflow of our teams. Although the process seemed to be long and with a lot of configurations, it doesn’t have to be like that always. We can start creating packages only for the new code added to our application and then modularize gradually. So now we can start to talk about Gradual Modularization this is the concept introduced by Stephan Hagemann “We can, for the first time, decide to start modularizing a portion of the code in an aspirational way… This allows us to create a gradually expanding support system towards a better application structure”.
続きを読む