The paper

In the infoQ wechat group the other night, Limboy from Mogujie made a share about the road to componentization of Mogujie. I don’t think this componentization is the right way for Mushroom Street. After sharing, I had a private chat with Limboy and Limboy seemed to understand the problem. I promised him THAT I would write an article about my solution, and this article came out.

In addition, componentization is logically part of the iOS App Architecture talk, but I didn’t plan to write componentization when I came up with the architecture talk because I forgot about it… Later, WHEN I wrote about View, I remembered, so I added some content at the end of the article about View. And I think the componentization scheme is too simple, including the components to implement the componentization scheme is also very simple, the code is only 100 lines including comments, I am lazy to let go, after all, writing an article is so tired.

In this paper, the modular scheme demo https://github.com/casatwy/CTMediator down here remember pod after install down recall pod install to pull down the pod after install, This Demo is easy to deal with business-sensitive boundary cases, which need to be done according to the characteristics of different apps and requirements of different products, so it is only used to illustrate the componentized architecture. If you want to use it in a real scenario, you can modify it according to the comments in the code.

The original address of Mushroom Street can not be opened. If you have not finished reading the original text, I will briefly introduce how to do the componentization of Mushroom Street:

  1. Component modules are instantiated when the App starts, and then these components register urls with the ModuleManager, sometimes without instantiation, using class registration.
  2. When component A needs to call component B, the URL is passed to the ModuleManager and the parameters are passed as GET with the URL, similar to openURL. The ModuleManager is then responsible for scheduling component B and finally completing the task.

There are problems in each of these two steps.

The problem with the first step is that registering urls is not a necessary and sufficient condition for componentization, and components do not need to register urls with the component manager. In addition, after registering a Url, it will cause unnecessary memory resident, which is smaller if you only register a Class, and larger if you register an instance. As for whether mogujie registered a Class or an instance, Limboy didn’t say when sharing it. I didn’t see it in the article either, maybe I missed it. But that’s not a fatal mistake, it’s a minor flaw.

The real fatal mistake is in the second step. In the iOS world, it must be the componentized middleware that services openUrl, not the openUrl approach.

What does that mean?

In other words, the componentization scheme of an App must not be built on THE URL, and cross-APP calls of openURL can be built on the componentization scheme. Of course, if the App is not componentized, openURL can also be built, just a little ugly.

Why do you say so?

Due to the complexity of the problems to be dealt with and the complexity of the process of disassembling and scheduling services during the implementation of the componentization scheme, openURL alone is not capable of enabling an App to implement the componentization architecture. There is a fatal flaw in the implementation of componentalization scheme based on openURL for App: unconventional objects cannot participate in local inter-component scheduling. I’ll make a distinction about unconventional objects when I go into the componentization scheme.

In the actual App scenario, if URL invocation in GET mode is used between local components, two problems will occur:

  • There is no way to express unconventional objects

For example, if you want to call an image editing module, you can’t pass UIImage to the corresponding module, which is a very sad thing. Of course, this can be solved by passing a new parameter to the method. For example:

[a openUrl:"http://casa.com/detail?id=123&type=0"];
Copy the code

At the same time, provide this method:

[a openUrl:"http://casa.com/detail" params:@{
    @"id":"123",
    @"type":"0",
    @"image":[UIImage imageNamed:@"test"]
}]
Copy the code

If you do not do this, complex and unconventional parameters will not be passed. If you do that, then this is actually the entry point for separating remote and local calls, which becomes what I’m advocating in this article, and what the Mushroom Street solution doesn’t do.

In addition, it is not necessary to use urls in local calls. If the business engineer needs to provide urls when scheduling between local calls, then it is inevitable to provide params when calling which params are easily confused by the business engineer… The demo code sample given in the second half of this article shows that business engineers do not need to know the URL when making local calls, and the demo code sample also illustrates how to solve the problem of business engineers getting confused when passing params.

  • URL registration is completely unnecessary for the implementation of componentization scheme, and the componentization scheme formed by THE way of URL registration, scalability and maintainability will be discounted

The purpose of registering a URL is actually a process of service discovery. In the iOS world, service discovery is done by using runtime instead of active registration. In addition, the maintenance of the registration part of the code is a relatively cumbersome matter, every time support for a new call, to maintain a registry. It is common to forget to delete items if a call is deprecated. Runtime Because there is no registration process, there are no maintenance operations and maintenance costs are reduced.

Because the service is automatically discovered through the Runtime, the task of expanding the calling interface only lies in the respective modules. Any new interface or new business is added without going to the main project for operation, which is very transparent.

A small summary

Mogujie used openURL to componentize the App, which was wrong, and it was unnecessary to use registration to discover the service. And there are other problems with this scheme, which you will be well aware of as the following introduction to the componentized scheme unfolds.

Correct componentization scheme

Let’s take a look at the architecture of the solution

-------------------------------------- | [CTMediator sharedInstance] | | | | openUrl: <<<<<<<<< (AppDelegate) <<<< Call From Other App With URL | | | | | | | | | |/ | | | | parseUrl | | | | | | | | | . |... | | | | | | | |/ | | | | performTarget:action:params: <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Call From Native Module | | | | | | | | | | | | |/ | | | | ------------- | | | | | | | runtime | | | | | | | ------------- | | . . | ---------------.---------.------------ . . . . . . . . . . . . . . . . -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | the | |. | |. | |. | |. | |. | |. | |. | | | | |  | Target | | Target | | | | | | / | \ | | / | \ | | / | \ | | / | \ | | | | | | Action Action Action ... | | Action Action Action ... | | | | | | | | | | | | | |Business A | | Business B | ------------------------------- --------------------------------Copy the code

This figure is a simplified architectural description of the componentization scheme, which is mainly based on Mediator mode and target-Action mode, with runtime used to complete the call. This componentization scheme splits the remote application call and the local application call, and the local application call provides services for the remote application call, which is just the opposite of the Mushroom Street scheme.

Call way

Let’s start with local application calls, Local component A calls [[CTMediator sharedInstance] performTarget:targetName Action :actionName params:@{…}] somewhere to initiate A cross-component call to CTMediator, CTMediator generates target instance and corresponding action selectors through The Objective-C Runtime transformation based on the obtained target and action information, and then finally calls the logic provided by the target business to fulfill the requirements.

In the case of a remote app call, the remote app uses openURL, and iOS finds the app that responds to the URL (in this case, your own app) according to the Scheme configuration in info.plist. After the app receives the URL via an AppDelegate, The CTMediator’s openUrl: method is called to pass in the received URL information. Of course, ctMediators can also use openUrl:options: to receive options as well, depending on whether or not your local business executes logic that contains option data. After passing in the URL, CTMediator parses the URL to route the request to the appropriate target and action. The process then becomes the same as the local application call described above, and finally completes the response.

Routing operations for requests rarely use local files to record the routing table. The server often handles this service. In the server domain, route resolution is basically done through regular expressions. App route parsing can be done simply, and URL specification can also be completed. The simplest way is scheme://target/action. Simple string processing can extract target and action information from the URL.

Components expose callable interfaces only through actions

All components respond through their own target-action, that is to say, the interface between modules is solidified in the target-Action layer, which avoids the intrusion of Business in the implementation of componentized transformation and improves the maintainability of componentized interface.

-------------------------------- | | | Business A | | | --- ---------- ---------- --- | | | | | | | | | | | | . | |... | |... | |... . | | | | | | . . | | | | | | . . --- --- --- --- --- --- . . | | | | | | . . |action| |action| |action| . . | | | | | |  . . ---|---- -----|-- --|----- . . | | | . . | | | . . ----|------ --|--------|-- . . | | | | . . |Target_A1| | Target_A2 | . . | | | | . . ----------- -------------- . . . . . ..................................................Copy the code

As you can see, the dotted line is the target and action used for cross-component calls. This avoids the added complexity of providing inter-component calls directly from BusinessA, and any component that wants to provide the call service can simply hang target and Action. The business itself remains largely untouched in most scenarios for componentization.

Complex parameters and unconventional parameters, and componentized related design ideas

Here we need to make a unified understanding of terms:

Complex parameters are multilevel parameters that consist of common types of data. In this article, we define any type that can be parsed by JSON as a normal type, including NSNumber, NSString, NSArray, and NSDictionary, as well as related derived types such as NSMutableArray from the system or whatever you define yourself.

To summarize: In the scenario discussed in this article, complex parameters are defined as parameters with a complex structure made up of common types. A common type is defined as a type that can be parsed by JSON.

Unconventional parameters are parameters that consist of types other than normal types, such as UIImage, which cannot be parsed by JSON. Parameters composed of these types are then defined as unconventional parameters in the text.

To summarize: an unconventional parameter is a parameter that contains an unconventional type. The definition of an unconventional type is that any type that cannot be parsed by JSON is called an unconventional type.

Boundary case:

  • If any of the contents of the multilevel parameters are unconventional parameters, such parameters are also considered as unconventional parameters in this paper.
  • If a type cannot currently be parsed by JSON, but can be converted to JSON in some way, it is also called a normal type in the context of a scenario.

An example is a custom view described using JSON. If the view can be converted to JSON by a component, then even if the view itself is not a common type, it is still considered a common type in the context of a converter.

  • If there is no converter in the context, the view is an unconventional type.
  • Suppose the converted JSON cannot be converted to A View. For example, component A has A converter and component B does not have A converter. Therefore, json cannot be converted to A View in component B during inter-component calls. In this call direction, we still consider the view a normal type as long as the caller can convert an unconventional type to JSON. If the caller is component A and the converter is in component B, the view is considered an unconventional type, even if it can be converted to JSON in component B.

Then I’ll explain why remote application calls should be supported by local intercomponent calls:

When the remote App is called, it is impossible for the remote App to provide unconventional parameters through URL. At most, the remote App can only provide complex parameters through URLEncode in the form of JSON string and then through GET, and then parse JSON in the local component to complete the call. Calls between components, through performTarget: action: params: is the ability to provide unconventional parameters, so we can know, and remote App called context and function is the local component calls between a subset of the context and function.

Therefore, this logic is destined to provide services for remote App calls by local component calls. Only the design idea that conforms to this logic is the correct design idea of componentization scheme, and other ideas that are inconsistent with this logic must be wrong. Because it doesn’t logically make sense for a subset to serve its parent, forcing it to do so is summed up in an idiom called retroactive.

In addition, the remote App call must be separated from the local component call. The remote App call can only use the special remote method provided by CTMediator, and the local component call can only use the special local method provided by CTMediator. The two cannot be called through the same interface.

There are two reasons for this:

  • The remote App call handles the input parameter with one more URL resolution procedure than the local one, which is unique to the remote App call. I said this before, but I won’t go into detail here.
  • There are no necessary and sufficient conditions for the architect to assume that the way remote App calls handle unresponsive requests and the way local component calls handle unresponsive requests will be consistent over the evolution of future products

In remote App calls, the user enters the App through a URL, and when the App can’t service that URL, a common way to do this is to display a so-called 404 interface that says, “There’s nothing there right now, but you can browse elsewhere in the App.” This scenario is most common when users are using different versions of the App. For example, if an app with a URL of only version 1.1 can respond fully, and a 1.0 app can be invoked but cannot complete the response process, then the 1.0 app will display a 404.

In inter-component invocations, there are two scenarios to consider if you encounter an unanswerable request.

Scenario 1

‘t happen response to A request if the scene is in the process of development, such as the two components in the development at the same time, A call to the component B, the component B is still in the old version has not released the new version, so can’t response, so at this time of treatment can be relatively casual, just can reflect B module is the old version, In the end, it will certainly be found in the RC test. As long as the App has not been issued, it will be too late to deal with it.

Scenario 2

If the unresponsible request scenario occurs in a published App, it may end up showing a 404, just as it would in a remote App call. But you might have to do something extra, you might have to do something extra, you might not show 404, you might show something else, it’s up to the product manager.

So how does this scenario happen?

Let me give you an example: when a user bookmarks something in version 1.0 and then upgrades the App to version 1.1. It is possible that the data stored in the local persistence layer of 1.0 favorites will be inconsistent with the data stored in 1.1 favorites. At this point the user in the 1.1 version of the app for 1.0 collection of things done some operations, triggering the local calls between components, between the local call and collect data related to the project itself, then the call is likely to become unresponsive calls, handling at this time is not the same display a page is over 404, Because the user has already seen their favorite item, and you tell them they can’t find it, the user is immediately confused… There are many ways to do this, and as an architect, there is no way to predict which one the product manager will choose. If the requirements of the product manager are implemented to the architecture, the call entry is required and your architecture does not split the call entry, you have only two choices: either call back the product requirements or work overtime to split the call entry.

Of course, the architect can choose to beat back the requirements of the product manager and ultimately pick a requirement that his architecture can accommodate. But wouldn’t it be a shame if the demand for the product was met because of a hole you dug early in your architecture?

Given the difference in how unresponsive requests are handled under remote APP calls and local intercomponent calls, and the unknown future evolution of the product, splitting the remote APP call portal from local intercomponent call portal is a matter of great benefit in the future.


Demodel design in componentization scheme

When calling between components, parameters need to be de-modeled. If parameters are not modelized for inter-component calls, the business becomes componentized in form and is not essentially independent.

If module A and module B are called in A modelized way, the argument passed when calling A method will be an object.

If the object is not a general-purpose interface oriented object, the handling of Mediator parameters can be complicated because of the distinction between different object types. If mediator does not handle arguments and sends the object directly to module B as a stereotype, then module B must contain declarations of the object type. Assuming that object declarations are placed in module A, then componentization between B and A is just A formalism. If the object type declaration is placed on mediator, then B has to rely on Mediator. However, as you can see from the above architecture diagram, relying on Mediator is not a necessary condition for the module responding to the request, so such dependence is completely unnecessary, and the existence of such dependence is a pollution to the architecture as a whole.

If the parameter is an interface oriented object, the mediator’s handling of the parameter is not necessary, and is passed directly to the responder’s module. And the definition of the interface cannot be placed in the initiator’s module, only in mediators. To complete the response, the responder must also rely on Mediator. However, as I mentioned earlier, the responder’s reliance on Mediator is not necessary, so parameters are not suitable for passing as an interface oriented object.

Thus, the result of using objectified parameters, whether interface oriented or not, is that the business module is componentized in form, but still not independent in nature.

In this cross-module scenario, parameters are best passed in a de-modelized manner, which in iOS development is dictionary style. This allows only the caller to rely on the Mediator, while the responder does not need to. However, in the practice of de-modeling, due to the large degree of freedom in this way, we at least need to ensure that the parameters generated by the caller can be understood by the responder. However, in the componentized scenario, it is much easier to limit the degree of freedom of the de-modeling scheme compared with the network layer and persistence layer.

Because componentization naturally has a limited means: the wrong parameters can not be called! Direct debug will quickly find the cause if it cannot be called. So the next problem to solve in de-modeling is how to improve development efficiency.

In the model-free componentization scenario, there are two points that affect efficiency: how does the caller know which key parameters the receiver needs? How does the caller know which targets can be called? In fact, the latter problem will be encountered regardless of whether to model the scheme. Why put them together? Because I’m going to talk about a solution that solves both problems together.

The solution is to use categories

The Mediator repo maintains several classes for Mediators, each of which corresponds to a target, and the methods in each category correspond to all possible calling scenarios under the target. All available target-actions are automatically obtained, both for invocation and parameter passing, which is very convenient. Now I want to explain why category and not the other:

  • Category itself is a composite pattern that provides different methods for different categories, and each component is a category, so it makes sense to encapsulate the calls that each component can support in a category.
  • Parameter validation can be done in the category method, which is necessary to ensure parameter security in the architecture. When the parameters are wrong, a category provides an entry point to remedy the situation.
  • Categories make it easy to do request forwarding, but without them the request forwarding logic would be very difficult to do.
  • Categories unify the call entry between all components, thus providing engineers with great convenience in both debugging and source code reading.
  • Since categories unify all entry points, the scope of Hardcode for Param in the entire App is only within the category when it is called across modules, and hardcode in this scenario is no different from calling macros or calling declarations, and therefore acceptable.

Here’s what happens when the business side makes a category call, and you can see how convenient it is to not have to remember the URL and not have to worry about what parameters to pass.

if (indexPath.row == 0) { UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail]; // After the view Controller is obtained, it is up to the user to decide whether to push or present in this scenario, Mediator as long as it is good to give a view controller instance [self presentViewController: viewController animated: YES completion: nil]; } if (indexPath.row == 1) { UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail]; [self.navigationController pushViewController:viewController animated:YES]; } if (indexPath. Row == 2) {// If (indexPath. Row == 2) {// If (indexPath. Row == 2) {// If (indexPath. [[CTMediator sharedInstance] CTMediator_presentImage:[UIImage imageNamed:@"image"]] } if (indexpath.row == 3) {// If (indexpath.row == 3) {// If (indexpath.row == 3) {// If (indexpath.row == 3) {// If (indexpath.row == 3) {// If (indexPath.row == 3) {// If (indexPath.row == 3); } if (indexPath.row == 4) { [[CTMediator sharedInstance] CTMediator_showAlertWithMessage:@"casa" cancelAction:nil ConfirmAction :^(NSDictionary *info) {// do what you want NSLog(@"%@", info);}]; }Copy the code

Some additional measures have to be taken based on other considerations

For safety reasons

We need to prevent hackers from calling native components through URLS, such as the personal property page of Alipay. Without proper differentiation and security measures at the call level, it is possible for a hacker to view anyone’s personal property through Safari.

There are many security measures, most of which depend on the requirements of the App itself and the product. At the level of architecture, the most basic thing to do is to distinguish whether the call is from the remote App or the local component. In the demo, I add the native prefix to the action to do the security measure. Any action with a native prefix is only allowed to be called by the local component. If a method with a native prefix is called during the URL phase, you can take action. This is one of the important reasons to distinguish the remote APP call portal from the local component call portal.

Of course, there are many ways to ensure security, but once you separate out remote and local calls, there’s room for all sorts of things.

Based on dynamic scheduling considerations

Dynamic scheduling means that today I might have A jump to show page A, but tomorrow I might have the same jump to show page B. It could be a jump between local components or it could be a jump from a remote app.

There are a number of cutting points to do this in this architecture:

  1. The url parse is the pointcut
  2. The pointcut point is when target is instantiated
  3. Taking the category scheduling method as the tangent point
  4. Take the action under Target as the pointcut

If url parse is used as the pointcut, this dynamic scheduling can only affect remote App jumps and loses the ability to dynamically schedule local jumps, so it is not suitable.

If target instantiation is the pointcut, it is unnecessary to do a review of all targets in your code to see if they are scheduled. If only 1 out of 10 call requests is to be dynamically scheduled, then 10 checks must be made. Only after that check is passed, dynamic scheduling is implemented, which is a relatively rough method.

If the category scheduling method is used as the pointcut, dynamic scheduling can only affect the jump of the local component, because categories are only used locally, so it is not suitable.

Action under target is the most suitable pointcut, because dynamic scheduling has scope in general scenarios. Most of the active pages need dynamic scheduling, such as today’s activity and tomorrow’s activity, or today’s activity is in progress and tomorrow’s activity will end, so the demand for dynamic scheduling is generated. We review whether the current action needs to be dynamically scheduled in the action that may produce dynamic scheduling, and there is no need to review in the regular scheduling, such as the jump of personal home page, the jump of commodity details, etc., so that the efficiency can be relatively high.

As you can see, if you want to do dynamic scheduling like this, it is essential that the target-action layer is abstracted. However, Mogustreet does not abstract the target-action layer, which is one of the problems.

Of course, if your product requires all pages to have dynamic scheduling requirements, it is better to use target instantiation as the pointcut to schedule, so that each scheduling request can be reviewed for dynamic scheduling.

Having said the scheduling pointcuts, the next thing to say is how to complete the review process. There are several complete review processes, and I’ll list each of them:

  1. Download the scheduling list when the App starts, or download the scheduling list periodically. Then the review checks whether the current action has an action to be dynamically scheduled to jump, if so, jump to another action
  2. Each time a new action is reached, the API is called with the action parameter to know whether it needs to jump. If it needs to jump, the API tells it which action to jump to, and then jumps to the action specified by the API

In fact, both of these two approaches are ok. If the product has a high requirement for immediacy, the second solution is adopted. If the product does not have such a high requirement for immediacy, the first solution is ok. Since there is no URL registration list, the server only needs to provide the original target-action and the target-action corresponding to the jump. The whole process can not only be achieved by registering the URL list, and this scheme is easier to maintain than registering the URL list.

In addition, it is not impossible to use the means of URL rewrite to do dynamic scheduling. However, what I need to distinguish here is that the necessity of URL is only reflected in remote App scheduling, and there is no need to spread to local component calls. In this way, when we do THE URL routing of remote App (the current demo does not provide the URL routing function, but provides the ACCESS point of URL routing operation, which can be inserted according to business needs), there are many fewer things to care about, and it can be relatively clean. In this scenario, using URL rewrite alone is no different from using URL parse as the tangent point mentioned above.

In contrast, mogujie’s componentization scheme has the following defects

  • Mogoo Street does not split remote and local calls

Not separating remote calls from local calls makes it difficult to implement many of the following methods, as I’ve already discussed in the previous article. By the way, this is not split by source. For example, using a URL to distinguish between a remote App call and a local call only distinguishes the source of the caller.

OpenUrl ->urlParse-> Perform ->target-action Perform ->target-action; perform->target-action; The two must be distinguished, and the Mushroom Street scheme does not distinguish them well.

  • Mogustreet services inter-local calls in the form of remote calls

This is putting the cart before the horse, making it difficult for future architectures to support business growth. As discussed earlier, in the iOS scenario, the implementation of remote calls is a subset of the implementation of local calls, only the large ones serve the small ones, i.e. local calls serve remote calls, and the reverse is the reverse.

  • Mogulstreet’s local to local calls cannot pass unconventional parameters, and complex parameters are passed in an ugly way

Note the distinction between complex and unconventional parameters.

Because local calls are performed as remote calls, the relationship between the two sets of functionality was discussed earlier, this approach does not meet the need to pass unconventional parameters. Moreover, if this method does not change, complex parameters can only be passed through the URlencode JSON string, which is ugly and not easy to debug.

  • Mogujie must register URL responders when the app is launched

This condition is not necessary in a componentized scenario, as demonstrated by demo. This unnecessary operation leads to unnecessary maintenance costs, which is not a big deal from a purely business perspective. It depends on whether the architects are being hard on themselves.

  • When adding componentized call path, the operation of Mushroom street is relatively complex

In the componentization scenario presented in this article, the only thing the responder needs to do is provide Target and Action, and there is no need to do anything else. Mushroom street in addition to do a lot of additional unnecessary measures to ensure the success of the call.

  • Mogustreet does not encapsulate the Target layer

This approach makes all cross-component invocation requests hit directly to the business module, which inevitably becomes bloated and difficult to maintain and has an intrusive architecture. In this way, the irrelevant code will not invade into the original business components, the future migration and modification of business components will not be affected by component invocation, and the time cost brought by componentized implementation of the project will be reduced.

conclusion

The componentization scheme provided in this paper adopts Mediator mode and Target-Action mode under Apple system.

One minor drawback of this solution, however, is hardcode for Param’s key, which is a trade-off for maximum decoupling and flexibility. There are no Hardcode scenarios in either my network or persistence architectures, which is another way to illustrate the specificity of componentized architectures.

When weighing, consider that the influence domain of hardcode only exists in mediator category. In this case, Hardcode is completely transparent to the caller. For the responder, the processing is equivalent to the processing of the parameters returned by the API, and the processing of the responder is limited to the Action.

Therefore, the existence of Hardcode in this part is somewhat unclean, but it is acceptable in terms of trade-offs compared to other benefits of this impurity. If hardcode is not adopted, it will inevitably cause the responder to rely on Mediator as well, which is logically unnecessary. In addition, this part of Hardcode has no impact on the actual use of my various projects.

Also, the reason harcode is present in the componentized scenario, and the de-modeling of the network and persistence layers does not occur with Hardcode, is because all the recipients and callers of the componentized call are in the same context. The network layer has one side on the server and the persistence layer has one side on the database. Plus, the design-time improvements to hardcode go beyond the limits of the language itself. In other words, Harcode is limited by the language itself. Both Objective-C and Swift have a flawed interface design philosophy. If we assume that in the context of Golang, it is completely possible to use Golang interface system to make a most beautiful architecture scheme. However, this is beyond the scope of this article, interested students can go to learn about the relevant knowledge. Architectural design is sometimes helpless.

Componentalization plan is very important to implement when App business is stable and the scale (business scale and development team size) is growing. It helps divide and rule complex App, and also contributes to collaborative development of multi-person large teams. However, componentization is not a good idea to implement too early in a volatile business situation, at least until the product has passed the MVP stage. Service instability means link instability. Componentization on an unstable link will complicate global module scheduling and reconstruction when the main service changes in the future.

When decided to implement componentization plan, architecture design for componentization schemes can directly affect the architecture system can support the development of future business, all over the componentization of App is more than just code and across business down the page, but also complex and unconventional business parameters involved in scheduling, scheduling of pages across components function, component scheduling security, Decoupling between components, change of old and new business call interface, etc.

Mushroom street componentized solution only across business page calls demand, essentially only realized my article in the view layer architecture across business calls page content, this is not to the point of becoming componentized solution, and mushroom street componentized solution from real App componentized requirements or sent a certain distance, and the design logic defects, Hope mushroom street can step up reconstruction, to create a real componentization program.

Patch:

I felt that I was too sketchily describing the modularization scheme for Mogustreet at the beginning of this article, and I also overlooked the fact that there was a ModuleManager, so I will redescribe it here.

Mushroom Street does cross-component operations in two ways

The first is through MGJRouter registerURLPattern: toHandler: register, URL and block binding. This method is preceded by a URL, such as MGJ ://detail? ToHandler: passes a block that ^(NSDictionary *routerParameters){// can do anything}.

When the component to perform [MGJRouter openURL: @ MGJ: / / detail? Id = “404”], according to previous registerURLPattern: toHandler: information, before you find through toHandler: collect block, Then pass the GET argument from the URL, id=404, into the block for execution. If you execute NSLog(routerParameters) in a block, you see @{@”id”:@”404″}, so the business in the block can be executed.

Then, in order for the business side to not write the URL, Mogustreet lists a series of macros or string constants (I am not sure whether it is a macro or string, I have not read the source code, but limboy mentioned that a backend system generates a source file full of urls) to represent the URL. In openURL, no matter remote application call or local component call, as long as the parameters passed are not complex, openURL will be adopted to invoke the page, because complex parameters and unconventional parameters cannot be supported.

The downside: this registration is unnecessary and hogs up memory using urls and blocks for nothing. Another problem is that even for simple parameter passing, if there are many parameters, the business engineer cannot know which parameters to pass without looking at the original URL string.

The reason why Mogujie uses id=: ID, I guess it is to avoid problems caused by the service engineer passing multiple parameters in different order, so it uses a placeholder. This is more common when the persistence layer generates SQL strings. However, I did not see this function written in limboy articles, do not know whether it is implemented.

In the componentization scenario presented in this article, there is no memory problem because there is no registration. Because interface calls are provided through categories, there are no arguments. In the case of Mogustreet, this approach does not achieve the purpose of separating remote application calls from local component calls, and the problems caused by not splitting are already covered in this article.


Since the previous openURL method cannot pass unconventional parameters, there is a second registration method: Opened a new object called ModuleManager, provides a registerClass: forProtocol: in the application starts up, the method of components will have a special ModuleEntry evoked, ModuleEntry then pairs @Protocol with Class. So there is a dictionary in the ModuleManager to record the pairing.

The business side will not use the [MGJRouter openURL:@” MGJ ://detail? Id =404″] solution when there are calls involving unconventional parameters and will use the ModuleManager classForProtocol: method instead. The business passes an @protocol to the ModuleManager, which then looks up the corresponding Class in the previously registered dictionary and returns it to the business party, who then performs alloc and init methods to get an object that matches the @protocol just passed in. Then execute the corresponding logic.

Like MGJRouter, there is no need to register protocol and class names for ModuleManager. And whether the service provider calling registerClass: forProtocol: all right, caller calls classForProtocol: service, must rely on the same protocol. Mogulstreet puts all protocols into a single publicprotocol.h file, so both callers and responders must rely on the same file. I also discussed this in the article: responders do not need to rely on anyone else to provide services.


So my response to the componentization of Mushroom Street is as follows:

  • The so-called separation of remote application calls and local component calls is not true, the separation of normal parameter calls and unconventional parameter calls. The drawbacks of not distinguishing between remote application calls and local component calls have been discussed in this article and won’t be covered here.

  • Mogujie does not only have openURL mode, but also provides ModuleManager mode. However, the so-called “inter-component call” and “inter-page jump” are actually divided into two dimensions. As long as the APP responds to a URL, whether inside or outside the APP, it can be. Intercomponent calls go the other way, so there are no security issues. In fact, this is also not true, because openURL method also appears in the local inter-component call, which has been mentioned in the section of inter-component communication in his first article. This may cause security problems. It also acknowledges that openURL is used for local component calls, which confirms my first point.

  • According to the above two points, under the openURL scenario, Mogujie still has the problem of providing services for local inter-call in the way of remote call, which I have also discussed in the paper.

  • Mogujie calls between local uses both openURL scheme and protocol-class scheme, so in fact, I was wrong to point out that mogujie calls between local cannot pass unconventional parameters and complex parameters. It should be used openURL when mogujie calls between local uses ordinary parameters. If it is an unconventional parameter, use protocol-class, which is obviously bad for the management and maintenance of local calls…

  • Limboy says that having to register URL responders at app launch is inevitable, but doesn’t say why. My demo has confirmed that registration is not necessary, so I’d like to hear limboy explain why.

  • Your architecture picture is wrong

According to your scheme, the red circle is impossible without dependency…

In addition, Limboy also put forward some views on the scheme of this paper:

Think of categories in a sense as a registration process.

In fact, the registration of Mogujie and my category here are two different things, and I can’t understand the logic of equating the category with the registration URL in any way 😂

One simple fact proves that the two are not equivalent at all: my scheme can run without a category, but the business side calls are uglier. If mogujie does not register URL, the whole process will not run ~

It is believed that the advantage of openURL is that it can care less about business logic, and the advantage of the scheme in this paper is that it can easily complete parameter passing.

I don’t think the solution in this paper cares more about business logic than openURL, because both of them, by comparison, are passing parameters and sending call requests. Under the condition of caring about business logic, they are exactly the same. The only difference is that I can pass unconventional parameters and openURL can’t. In the whole process of this solution, there is no business logic belonging to the responder on the caller’s side.

Consider protocol/URL registration equivalent to abstracting target-action out of the calling interface

The real difference is that protocol invades the business and does not conform to the black box model.

  • Let me explain why protocol intrudes into business

Because an object in a business needs to be invoked, it must conform to a protocol that can be invoked. However, this protocol does not exist in the current business domain, so the current business has to rely on publicProtocol. This has a significant impact on future business migration.

  • Also explain why it doesn’t fit the black box model

Mogujie’s protocol method makes the object be used in the caller. Since the caller does not contain the business domain in which the object originally resides, when multiple such objects are needed to complete the task, it is necessary to obtain class through protocol for multiple times to instantiate multiple objects and finally complete the requirement.

However, the target-action pattern ensures that the response to inter-component calls is executed in the responder context, which is the biggest difference compared to Mogustreet’s Protocol scheme. Because in black-box theory, the caller only initiates the request and the responder is responsible for the execution of the request, the execution logic must exist in the context of the responder, not the caller.

For example, when you make a web request, the back end takes the data and renders the page, no matter how many channels are involved in retrieving the data, the logic of retrieving the data is done on the server side and then returned to the browser for display. This is the right thing to do, and target-Action mode does the same thing.

But Mushroom Street’s solution looks like this: You launched a network request, the back-end data returned is not, return of unexpectedly is a data access object (DAO), and then you through the DAO to fetch data, to render a page, if the page rendering process involves multiple DAO, so you have to initiate more requests, or get the DAO, then take this DAO to obtain data, Then render the page. This is a very weird way to do it…

If the purpose of doing this is to determine the next logical direction of the business based on the return value of the intermediate stage, it should be multiple calls to get the data and then determine the next business direction, not every time you get a DAO… It’s natural to use target-action for this scenario

Therefore, to sum up, There are big problems in Mogujie’s plan. I hope Mogujie can continue to correct them

The last

IOS Application architecture on componentization is a 16-year architecture article, but I believe it is still very useful today