2024-12-25 17:31:27
2024-04-01 11:42:21
朋友圈看到中学老师的书法作品。
版本比较多,找了一个类似的。
【梁】志公和尚(宝志禅师) 撰
南来北往走西东,看得浮生总是空。
天也空来地也空,人生渺渺在其中。
大地本来无寸土,人生劳碌一场空。
日也空来月也空,来来往往不留踪。
日月星辰常运转,人亡千载永无踪。
山也空来水也空,随缘变化体无穷。
青山绿水依然在,为人一死不相逢。
田也空来地也空,换了多少主人翁。
世间多少穷后富,也有多少富后穷。
金也空来银也空,死后何曾在手中。
万两黄金拿不去,为他一世受牢笼。
生也空来死也空,生死如同一梦中。
生如百花逢春好,死如黄叶落秋风。
夫也空来妻也空,大限到了各西东。
夫妻本是同林鸟,可怜死后不相逢。
男也空来女也空,黄泉路上难相逢。
贵子贤孙休贪爱,人因痴爱堕牢笼。
幻化空身虚变现,空是色来色是空。
空手来了空手去,到头总是一场空。
朝走西,暮走东,人生恰似采花蜂。
采得百花成蜜后,一场辛苦一场空。
夜深听得三更鼓,翻身不觉五更钟。
从头仔细想一想,便是南柯一梦中。
不信但看桃李树,花开能有几时红。
任你做到公卿相,死后还归泥土中。
身归土,气归风,一片顽皮裹臭脓。
败坏不如猪狗相,何不当初问志公。
生有一,死无二,休向人前夸伶俐。
生前置下田万顷,死后只得三步地。
宽八尺、长丈二,仔细思量真个是。
若人死后带得去,志公与你亲书契。
2023-04-21 15:53:23
/boot
目录uname -r
dpkg --list 'linux-image*' | grep ^ii
sudo apt-get remove linux-image-VERSION
sudo apt-get autoremove
sudo update-grub
/lib/modules
目录apt list --installed linux-image
sudo rm -rf 5.15.0-70-generic/
#!/bin/sh
LANG=en_US.UTF-8 snap list --all | awk '/disabled/{print $1, $3}' |
while read pkg revision; do
sudo snap remove "$pkg" --revision="$revision"
done
2023-02-17 13:46:44
Porto is a modern software architectural pattern, consisting of guidelines, principles and patterns to help developers organize their code in a highly maintainable and reusable way.
Porto is a great option for medium to large sized web projects, as they tend to have higher complexity over time.
With Porto developers can build super scalable monolithics, which can be easily splitted into multiple micro-services whenever needed. While enabling the reusability of the business logic (Application Features), across multiple projects.
Porto inherits concepts from the DDD (Domain Driven Design), Modular, Micro Kernel, MVC (Model View Controller), Layered and ADR (Action Domain Responder) architectures.
And it adheres to a list of convenient design principles such as SOLID, OOP, LIFT, DRY, CoC, GRASP, Generalization, High Cohesion and Low Coupling.
It started as an experimental architecture, aiming at solving the common problems web developers face, when building large projects.
Feedbacks & Contributions are much appreciated.
“Simplicity is prerequisite for reliability.” — Edsger Dijkstra
At its core Porto consists of 2 layers “folders” Containers
& Ship
.
These layers can be created anywhere inside any framework of choice.
(Example: in Laravel or Rails they can be created in the app/
directory or in a new src/
directory at the root.)
Before diving deeper, let’s understand the different levels of code we will have in your code base:
Ship
layer.Containers
layer.The Containers layer (cargo containers) >> relies >>
on the Ship layer (cargo ship) >> relies >>
on the Framework (sea).
Porto is designed to scale with you! While most companies shift from Monolithic to Micro-Services (and most recently Serverless) as they scale up. Porto offers the flexibility to deflate your Monolithic into Micro-Services (or SOA) at any time with the least effort possible.
In Porto terms a Monolithic is equal to one cargo ship of Containers, while Micro Services is equal to multiple cargo ships of Containers. (Disregarding their sizes).
Porto offers the flexibility to start small with a single well organized Monolithic service and grow whenever you need, by extracting containers into multiple services as your team grows.
This is possible becuase Porto organizes your code into Containers, which are grouped into isolated Sections. A section can be later extracted out with all it’s related containers to be depolyed separatly as you scale.
As you can imagine operating two or more ships in the sea rather than a single one, will increase the cost of maintenance (two repositories, two CI pipelines,…) but also gives you flexibility, where each ship can run at different speed and direction. This technially translates to each service scaling differently based on the traffic it expect.
How Sections “Services” communicate together is completely up to the developers, even though Porto recomands using Events and/or Commands.
The Ship layer, contains the Parent “Base” classes (classes extended by every single component) and some Utility Code.
The Parent classes “Base classes” of the Ship layer gives full control over the Container’s Components (for example adding a function to the Base Model class, makes it available in every Model in your Containers).
The Ship layer, also plays an important role in separating the Application code from the Framework code. Which facilitates upgrading the Framework without affecting the Application code.
In Porto the Ship layer is very slim, it does NOT contain common reusable functionalities such as Authentication or Authorization, since all these functionalities are provided by Containers, to be replaced whenever needed. Giving the developers more flexibility.
The Ship layer, contains the following types of codes:
Note: All the Container’s Components MUST extend or inherit from the Ship layer (in particular the Parent’s folder).
When separating the Core to an external package, the Ship Parents should extend from the Core Parents (can be named Abstract, since most of the them supposed to be Abstract Classes). The Ship Parents holds your custom Application shared business logic, while the Core Parents (Abstracts) holds your framework common code, basically anything that is not business logic should be hidden from the actual Application being developed.
Porto manages the complexity of a problem by breaking it down to smaller manageable Containers.
The Containers layer is where the Application specific business logic lives (Application features/functionalities). You will spend 90% of your time at this layer.
A Container can be a feature, or can be a wrapper around a RESTful API resource, or anything else.
“In a TODO App, the ‘Task’, ‘User’ and ‘Calendar’ objects each would live in a different Container, were each has its own Routes, Controllers, Models, Exceptions, etc. And each Container is responsible for receiving requests and returning responses from whichever supported UI (Web, API..).”
It’s advised to use a Single Model per Container, however in some cases you may need more than a single Model and that’s totally fine. (Even if you have a single Model you could also have Values “AKA Value Objects” (Values are similar to Models but that do not get represented in the DB on their own tables but as data on the Models) these objects get built automatically after their data is fetched from the DB such as Price, Location, Time…)
Just keep in mind two Models means two Repositories, two Transformers, etc. Unless you want to use both Models always together, do split them into 2 Containers.
Note: if you have high dependecies between two containers by nature, than placing them in the same Section would make reusing them easier in other projects.
If you look at Apiato (the first project implementing Porto), you will notice that Authentication and Authorization are both features provided as Containers.
Container 1
├── Actions
├── Tasks
├── Models
└── UI
├── WEB
│ ├── Routes
│ ├── Controllers
│ └── Views
├── API
│ ├── Routes
│ ├── Controllers
│ └── Transformers
└── CLI
├── Routes
└── Commands
Container 2
├── Actions
├── Tasks
├── Models
└── UI
├── WEB
│ ├── Routes
│ ├── Controllers
│ └── Views
├── API
│ ├── Routes
│ ├── Controllers
│ └── Transformers
└── CLI
├── Routes
└── Commands
If you use Event based communcations between containers, you could use the same mechanism after spliting your code base into multi services.
Note: If you’re not familiar with separating your code into Modules/Domains, or for some reason you don’t prefer that approach. You can create your entire Application in a single Container. (Not recommended but absolutely possible).
Section are another very important aspect in the Porto architecture.
A Section is a group of related containers. It can be a service (micro or bigger), or a sub-system within the main system, or antyhing else.
Think of a Section as a rows of containers on a cargo ship. Well organized containers in rows, speeds up the loading and unloading of related containers for a specific customer.
The basic definition of a Section is a folder that contains related Containers. However the benifits are huge. (A section is equivalent to a bounded context from the Domain-driven design) Each section represents a portion of your system and is completely isolated from other sections.
A Section can be deployed separatly.
If you’re building a racing game like Need for Speed, you may have the following two sections: the Race Section and the Lobby Section, where each section contains a Car Container and a Car Model inside it, but with different properties and functions. In this example the Car Model of the Race section can contain the business logic for accelerating and controlling the car, while the Car Model of the Lobby Section contains the business logic for customizing the car (color, accessories..) before the race.
Sections allows separating large Model into smaller ones. And they can provide boundaries for different Models in your system.
If you prefer simplicity or you have only single team working on the project, you can have no Sections at all (where all Containers live in the containers folder) which means your project is a single section. In this case if the project grew quickly and you decided you need to start using sections, you can make a new project also with a single section, this is known as Micro-Services. In Micro-Services each section “project portion” live in its own project (repository) and they can communicate over the network usually using the HTTP protocol.
In a typical e-commerce application you can have the following sections: Inventory Section, Shipping Section, Order Section, Payment Section, Catalog Section and more…
As you can imagine each of these Sections can be a micro-service by itself. And can be extracted and deployed on its own server based on the traffic it receives.
In the Container layer there’s a set of Components
“Classes” with predefined responsibilities.
Every single piece of code you write should live in a Component (class function). Porto defines a huge list of those Components for you, with a set guidelines to follow when using them, to keep the development process smooth.
Components ensures consistency and make your code easier to maintain as you already know where each piece of code should be found.
Every Container consists of a number of Components, in Porto the Components are split into two Types:
Main Components
and Optional Components
.
You must use these Components as they are essential for almost all types of Web Apps:
Routes - Controllers - Requests - Actions - Tasks - Models - Views - Transformers.
Views: should be used in case the App serves HTML pages.
Transformers: should be used in case the App serves JSON or XML data.
A basic API call scenario, navigating through the main components:
Endpoint
in a Route
file.Endpoint
calls a Middleware
to handle the Authentication.Endpoint
calls its Controller
function.Request
injected in the Controller
automatically applies the request validation & authorization rules.Controller
calls an Action
and pass each Request
data to it.Action
do the business logic, OR can call as many Tasks
as needed to do the reusable subsets of the business logic.Tasks
do a reusable subsets of the business logic (A Task
can do a single portion of the main Action).Action
prepares data to be returned to the Controller
, some data can be collected from the Tasks
.Controller
builds the response using a View
(or Transformer
) and send it back to the User.Click on the arrows below to read about each component.
Routes are the first receivers of the HTTP requests.
The Routes are responsible for mapping all the incoming HTTP requests to their controller’s functions.
The Routes files contain Endpoints (URL patterns that identify the incoming request).
When an HTTP request hits your Application, the Endpoints match with the URL pattern and make the call to the corresponding Controller function.
Controllers are responsible for validating the request, serving the request data and building a response. Validation and response, happens in separate classes, but triggered from the Controller.
The Controllers concept is the same as in MVC (They are the C in MVC), but with limited and predefined responsibilities.
You may wonder why we need the Controller! when we can directly call the Action from the Route. The Controller layer helps making the Action reusable in multiple UI’s (Web & API), since it doesn’t build a response, and that reduces the amount of code duplication across different UI’s.
Here’s an example below:
W-R1
-> Controller W-C1
-> Action A1
.A-R1
-> Controller A-C1
-> Action A1
.As you can see in the example above the Action A1
was used by both routes W-R1
and A-R1
, with the help of the Controllers layer that lives in each UI.
Requests mainly serve the user input in the application. And they are very useful to automatically apply the Validation and Authorization rules.
Requests are the best place to apply validations, since the validations rules will be related to every request. Requests can also check the Authorization, e.g. check if this user has access to this controller function. (Example: check if a specific user owns a product before deleting it, or check if this user is an admin to edit something).
Actions represent the Use Cases of the Application (the actions that can be taken by a User or a Software in the Application).
Actions CAN hold business logic or/and they orchestrate the Tasks to perform the business logic.
Actions take data structures as inputs, manipulates them according to the business rules internally or through some Tasks, then output a new data structures.
Actions SHOULD NOT care how the Data is gathered, or how it will be represented.
By just looking at the Actions folder of a Container, you can determine what Use Cases (features) your Container provides. And by looking at all the Actions you can tell what an Application can do.
run()
.run()
can accept a Request Object in the parameter.The Tasks are the classes that hold the shared business logic between multiple Actions accross different Containers.
Every Task is responsible for a small part of the logic.
Tasks are optional, but in most cases you find yourself in need for them.
Example: if you have Action 1 that needs to find a record by its ID from the DB, then fires an Event. And you have an Action 2 that needs to find the same record by its ID, then makes a call to an external API. Since both actions are performing the “find a record by ID” logic, we can take that business logic and put it in it’s own class, that class is the Task. This Task is now reusable by both Actions and any other Action you might create in the future.
The rule is, whenever you see the possibility of reusing a piece of code from an Action, you should put that piece of code in a Task. Do not blindly create Tasks for everything, you can always start with writing all the business logic in an Action and only when you need to reuse it, create an a dedicated Task for it. (Refactoring is essential to adapt to the code growth).
run()
. However, they can have more functions with explicit names if needed. Making the Task class replace the ugly concept of function flags. Example: the FindUserTask
can have 2 functions byId
and byEmail
, all internal functions MUST call the run
function. In this example the run
can be called at the end of both funtions, after appending Criteria to the repository.FindUserByIdAction
and FindUserByEmailAction
where both Actions are calling the same Task” as well as it’s totally fine to have single Action FindUserAction
making a decision to which Task it should call.The Models provide an abstraction for the data, they represent the data in the database. (They are the M in MVC).
Models are responsible for how the data should be handled. They make sure that data arrives properly into the backend store (e.g. Database).
Views contain the HTML served by your application.
Their main goal is to separate the application logic from the presentation logic. (They are the V in MVC).
Transformers (are the short name for Responses Transformers).
They are equivalent to Views but for JSON Responses. While Views takes data and represent it in HTML, Transformers takes data and represent it in JSON.
Transformers are classes responsible for transforming Models into Arrays.
Transformers takes a Model or a group of Models “Collection” and converts it to a formatted serializable Array.
Exceptions are also a form of output that should be expected (like an API exception) and well defined.
SubActions are designed to eliminate code duplication in Actions. Don’t get confused! SubActions do not replace Tasks.
While Tasks allows Actions to share a piece of functionality. SubActions allows Actions to share a sequence of Tasks.
The SubActions are created to solve a problem. The problem is: Sometimes you need to reuse a big chunk of business logic in multiple Actions. That chunk of code is already calling some Tasks. (Remember a Task SHOULD NOT call other Tasks) so how shall you reuse that chunk of code without creating a Task! The solution is create a SubAction.
Detailed Example: assuming an Action A1
is calling Task1, Task2 and Task3. And another Action A2
is calling Task2, Task3, Task4 and Task5. Notice both Actions are calling Tasks 2 and 3. To eliminate code duplication we can create a SubAction that contains all the common code between both Actions.
run()
.You can add these Components when you need them, based on your App needs, however some of them are highly recommended:
Tests - Events - Listeners - Commands - Migrations - Seeders - Factories - Middlewares - Repositories - Criteria - Policies - Service Providers - Contracts - Traits - Jobs - Values - Transporters - Mails - Notifications…
A Container with a list of Main and Optional Components.
Container
├── Actions
├── Tasks
├── Models
├── Values
├── Events
├── Listeners
├── Policies
├── Exceptions
├── Contracts
├── Traits
├── Jobs
├── Notifications
├── Providers
├── Configs
├── Mails
│ ├── Templates
├── Data
│ ├── Migrations
│ ├── Seeders
│ ├── Factories
│ ├── Criteria
│ ├── Repositories
│ ├── Validators
│ ├── Transporters
│ └── Rules
├── Tests
│ ├── Unit
│ └── Traits
└── UI
├── API
│ ├── Routes
│ ├── Controllers
│ ├── Requests
│ ├── Transformers
│ └── Tests
│ └── Functional
├── WEB
│ ├── Routes
│ ├── Controllers
│ ├── Requests
│ ├── Views
│ └── Tests
│ └── Acceptance
└── CLI
├── Routes
├── Commands
└── Tests
└── Functional
The benefits of using Porto.
In Porto, your application business logic lives in Containers. Porto Containers are similar in nature to the Modules (from the Modular architecture) and Domains (from the DDD architecture).
Containers can depend on other Containers, similar to how a layer can depend on other layers in a layered architecture.
Porto’s rules and guidelines minimizes and defines the dependecies directions between Containers, to avoid circular references between them.
And it allows the grouping of related Containers into sections, in order to reuse them together in different projects. (each Section contains a reusable portion of your application business logic).
In terms of dependency management, the developer is free to move each Container to its own repository or keep all Containers together under single repository.
Porto aim to reduce maintance cost by saving developers time. It’s structured in a way to insure code decoupling, and forces consistency which all contribute to its maintainability.
Having a single function per class to describe a functionality, makes adding and removing features an easy process.
Porto has a very organized code base and a zero code decoupling. In addition to clear development workflow with predefined data flow and dependencies directions. That all contributes to its scalability.
Extremely adhering to the single responsibility principle by having single function per class, results in having super slim classes, which leads to easier testability.
In Porto each component expect the same type of input and output, which makes testing, mocking and stabbing very simple.
The Porto structure itself makes writing automated tests a smooth process. As it has a tests
folder at the root of each Container for contaning the unit tests of your Tasks.
And a tests
folder in each UI folder for contaning the functional tests (for testing each UI’s separately).
The secret of making the testing and debugging easy, is not only in the organization of the tests and pre defined responsiblity of the components but also in the decoupling of your code.
With Porto you can easily accommodate future changes with the least amount of efforts.
Let’s assume you have a web app that serves HTML and recently you decided that you need to have a Mobile app, hence you need an API.
Porto has pluggable UI’s (WEB, API & CLI) and this allows writting the business logic of your application first, then implementing a UI to interact with your code.
This gives the flexibility to adding interfaces whenever needed and adapting to future changes, with the least effort possible.
it is all possible because the Actions are the central organizing principle “not the controller” which are shared across multiple UI’s. And the UI’s are separated from the application business logic and separated from each others within each Container.
Porto makes it super easy to locate any feature/functionality. And to understand what’s happening inside it.
That due to the usage of the domain expert language when naming the classes “components”. As well as the single function per class golden rule. Which allows you to find any Use Case (Action
) in your code by just browsing the files.
Porto promises that you can find any feature implementation in less than 3 seconds! (example: if you are looking for where the user address is being validated - just go to the Address Container, open the list of Actions and search for ValidateUserAddressAction).
Porto’s takes future growth into consideration and it ensures your code remains maintainable no matter what the project size becomes.
It achieves this by its modular structure, separation of concerns and the organized coupling between the internal classes “Components”.
This allows modifications to be made without undesirable side effects.
Porto gives the ability to move quickly and easily.
It’s easy to make framework upgrades due to the complete separation between the App and the framework code through the Ship layer.
Feel free to list your implementation here.
List of projects implementing the Porto architecture.
Your feedback is important.
For feedbacks, questions, or suggestions? We are on Slack.
![]() Mahmoud Zalt Twitter: @mahmoudz Site: zalt.me |
Become a Github Sponsor.
Direct donation via Paypal.
Become a Patreon.
MIT © Mahmoud Zalt
2023-02-16 13:39:23
如题 apidoc 的 @apiGroup
值如果是中文的时候没办法进行分组。官方仓库提了好多这样的 issue,貌似韩文也不支持。
有人提了 pr,但是被拒绝了,不知道为什么。
可以临时根据这个 pr 修改文件来支持中文分组,node_modules/apidoc/lib/core/workers/api_group.js
文件中的:
group = group.replace(/^[^a-z]+|[^\w:.-]+/gi, '');
替换为:
group = encodeURI(group).replace(/^[^a-z]+|[^\w:.-]+/gi, '');
这样如果线上部署生成的话就不太方便了,可以在生成文档的脚本文件中添加一行替换的代码,在生成文档前强行替换:
sed -i 's/group = group.replace/group = encodeURI\(group\).replace/' node_modules/apidoc/lib/core/workers/api_group.js
2022-06-24 12:46:57
crud 惯了,从来没深入研究过这些(一直以为是能存储的字节数)。
查了一下官方文档:Numeric Type Attributes。
MySQL supports an extension for optionally specifying the display width of integer data types in parentheses following the base keyword for the type. For example, INT(4) specifies an INT with a display width of four digits. This optional display width may be used by applications to display integer values having a width less than the width specified for the column by left-padding them with spaces. (That is, this width is present in the metadata returned with result sets. Whether it is used is up to the application.)
MySQL 有一个支持在字段类型关键字后边的 ()
里指定 integer
数据类型的显示宽度的扩展。例如 INT(4)
指定一个显示宽度为4位数的整数。这个选项一般用在当字段值的宽度小于字段指定的宽度时,用空格对值进行左填充。(也就是说,此宽度存在于随结果集返回的元数据中。是否使用取决于应用程序。)
The display width does not constrain the range of values that can be stored in the column. Nor does it prevent values wider than the column display width from being displayed correctly. For example, a column specified as SMALLINT(3) has the usual SMALLINT range of -32768 to 32767, and values outside the range permitted by three digits are displayed in full using more than three digits.
显示宽度既不限制该字段可以存储的值的范围,也不会阻止比显示宽度宽的值的正确显示。例如一个字段指定为 SMALLINT(3)
,通常 SMALLINT
的范围为 -32768 到 32767,超出三位数宽度的值将会以三位数以上的数字全部显示。
When used in conjunction with the optional (nonstandard) ZEROFILL attribute, the default padding of spaces is replaced with zeros. For example, for a column declared as INT(4) ZEROFILL, a value of 5 is retrieved as 0005.
当与可选属性 ZEROFILL
一起使用时,默认的空格填充将替换为用 0
填充。例如一个字段声明为 INT(4) ZEROFILL
,那么值 5
将被检索为 0005
。
NOTE
The ZEROFILL attribute is ignored for columns involved in expressions or UNION queries.
If you store values larger than the display width in an integer column that has the ZEROFILL attribute, you may experience problems when MySQL generates temporary tables for some complicated joins. In these cases, MySQL assumes that the data values fit within the column display width.
注意
ZEROFILL
属性在表达式和联合查询中会被忽略。如果你在一个设置了
ZEROFILL
属性的整数字段中,存储的值大于显示宽度,你可能会在一些复杂的联合查询中生成临时表是碰到问题。在这些情况下,MySQL 假设数据值适合字段的显示宽度。
Excuse me 🤔️? 那这有啥用?
As of MySQL 8.0.17, the ZEROFILL attribute is deprecated for numeric data types, as is the display width attribute for integer data types. You should expect support for ZEROFILL and display widths for integer data types to be removed in a future version of MySQL. Consider using an alternative means of producing the effect of these attributes. For example, applications can use the LPAD() function to zero-pad numbers up to the desired width, or they can store the formatted numbers in CHAR columns.
从 MySQL 8.0.17
开始,数字数据类型不推荐使用 ZEROFILL
属性,整数数据类型也不推荐使用 display width
属性。你应该预料到在 MySQL 的未来版本中会删除对整数数据类型的 ZEROFILL
和 display width
的支持,考虑用其他方法来实现这些属性的功能。例如用 LPAD()
函数将数字填充为指定宽度,或者将格式化后的数字存储在 CHAR
类型的字段中。
弃用了我就放心了。