1 the introduction

DDD (Domain-driven Design) was first proposed by Eric Evans in 2004. With the increasing complexity of software systems, DDD has been widely used in back-end and mobile architectures in recent years. DDD has different architectural forms in different fields. For example, DDD focuses on distributed services at The back end. In microservice Architecture, DDD will be built around core services. Mobile terminal DDD architecture, though for many years history, mainly focus on page dimensions, as far as possible let the page rendering, interactive logic, storage and data request, little attention to the page UI elements within the dimensions, and the list is the most common mobile UI elements, almost every page has a list, most of the page after stripping list only a shell, Page rendering is actually a rendering of the list. For Android, MVVM only describes how to update the UI state of Activity/Fragment interface subscription, but does not specify how to update each item in RecyclerView. Typical VIPER architecture:

So what is a large list? Lofter, as an interest community for young people, pays more attention to the rich and efficient display of content to users. RecyclerView of many content aggregation pages contains more than ten types of text, pictures, music, video, long articles, live broadcast, small series recommendation and so on. Inside each type of view, there are many nested deep and interactive elements. Different types can also contain common elements, some elements even have different presentation styles in different page scenes, and the presentation state and arrangement relationship between these elements may still be dynamic. For example, the following is only an Item that focuses on a picture type in the stream:

For item of this image type, in the scenario of personal home page, the user information area in the head becomes date + collection:

Elements such as “like”, “like”, “favorite”, “comment”, “upper right corner menu operation”, “user information”, “comment display”, “audit status” are the same for other types of items (such as text, music, video, etc.).

The complexity of list rendering is far greater than the complexity of page dimensions designed by MVC/MVP/MVVM/MVI. Here we review the evolution of Lofter large list architecture to see how to use DDD to govern large list architecture.

2 Monomer Stage

The styles carried by this stage list are relatively simple, although there are multiple types, but whether you use your own recyclerView. Adapter extension or use the BRVAH open source library, the rendering code is often written in one Adapter and not difficult to maintain. Schematic diagram of architecture at this time:

Equivalent to writing switch-case statements in BRVAH convert:

public class LofterAdapter<T extends MultiItemEntity, K extends BaseViewHolder> extends BaseMultiItemQuickAdapter<T, K> { @Override public void convert(helper: K, item: T) { switch (item.type) { TEXT: ... break; PHOTO: ... break; VIDEO: ... break; MUSIC: ... break; }}}Copy the code

However, the deterioration of the architecture often starts from the single unit. With the increasingly complex business, The DashboardAdapter code of Lofter home page has reached more than 8000 lines, and many subclasses are derived due to the reuse of stream style in many pages. Once a release iteration includes the need for a stream of attention, the development schedule multiplies, so start splitting the Adapter while a release product redesigns the stream of attention.

3. Divide and conquer

We developed a split framework called ComMultiItemAdapter based on the BRVAH open source library. At the time, however, the BRVAH open source library did not support a split solution like BaseProviderMultiAdapter. Of course, in order not to affect future releases, we did not modify the BRVAH source code. All changes were implemented based on extensions. The construction stage of the list is no longer just associated with the item type and the item layout, but roughly written as follows:

addItemHolderController(TEXT, R.layout.text, new TextItemRender(adapterController));
addItemHolderController(PHOTO, R.layout.photo, new PhotoItemRender(adapterController));
addItemHolderController(MUSIC, R.layout.audio, new MusicItemRender(adapterController));
addItemHolderController(VIDEO, R.layout.video, new VideoItemRender(adapterController));
Copy the code

As we can see, each view type of the list is also associated with a separate render controller, which can get the loaded layout and entry data (the constructor passes in the adapterController’s role later).

Using TextItemRender as an example, the layout creation event is distributed to the doOnCreate method and the data binding event is distributed to the doOnBind method:

public class TextItemRender extends BaseRender { @Override public void doOnCreate(TextItemHolder holder) { ... } @Override public void doOnBind(TextItemHolder holder) { ... } } public class TextItemHolder extends BaseItemHolder { public TextView title; public TextView content; . }Copy the code

However, different types of items are not completely independent of each other. For example, text, picture, video and music cards all have elements of interactive operation such as like, recommend, collect, comment and menu items. As version iteration goes on, there are more and more common elements of such different types of cards, resulting in the growing size of BaseRender. The BaseRender class also has more than 2,300 lines of code, and its counterpart, ViewHolder, has 150 attributes that hold public View references to be displayed on all types of cards.

As we know, BRVAH allows an item to define its own ViewHolder type, so that during the item layout creation phase, the doOnCreate method, a reference to the View element can be pre-stored in the ViewHolder attribute with findViewById. The state of the View element can then be quickly updated during the data binding phase, as shown in the TextItemHolder above. This pattern is familiar and seems to work well, but when the item layout is complex, all View elements are arranged in the ViewHolder, resulting in a large number of attributes, and common elements between different types are arranged in the BaseItemHolder. In the end, it’s hard to figure out where the View elements we care about are.

When it comes to the specific page development, we will find that the rendering controller only has the above two methods, which are too thin for the business scenes it needs to meet, such as the lack of page life cycle, rolling event monitoring, picture loading, video playing and other basic capabilities. This is what the adapterController parameter is for in the render controller constructor. The RecyclerView Adapter Controller acts as an intermediary between the Activity and RecyclerView. In some special situations, it may also interact with Presenter/ViewModel. RecyclerView Adapter Controller will eventually become a “garbage dump”. A typical diagram is as follows:

At this stage, it looks like large lists have been managed, but the disadvantages are also obvious.

Note: The latest version of BRVAH supports the registration of various types of ItemProvider. ItemProvider is used for layout loading and data binding. Details are not described here.

Phase 4 DDD

In the divide-and-conquer stage, Lofter focused on the stream card style, which was not as complex as the introduction. Under the background of the data model reconstruction of the back-end interface and the revision of the attention stream according to the latest style, ComMultiItemAdapter framework in the divide-and-conquer stage could not adapt to the pace of the team’s business development. There is an urgent need for a large list governance framework that can meet the needs of collaborative development and maintenance by multiple people. Fortunately, we also have DDD, which has been around since its inception to deal with The complexity of software systems. Although DDD has produced standardized architectures on mobile like VIPER and The Clean Architecture, there are few good examples in The more niche area of large list governance. So, is it possible to govern large lists directly with The Clean Architecture? When you think about it more deeply, you will find various incompatibilities. This is because the essence of software architecture is to serve business scenarios. The same architecture idea will inevitably have different or even specialized forms in different business scenarios, which means that any existing DDD architecture cannot be applied to large-scale list governance.

4.1 DDD planning

The division of bounded context is the most critical step in DDD modeling stage. When we look back at RecyclerView image type cards, we divide the cards into many bars. Some bars are common to multiple types of cards. For example, clicking the “Favorites” button in the action bar will show a “favorites” prompt at the bottom of the content, but the business boundary of BAR is very clear, so bar is the most appropriate limiting context for DDD. Once we defined the bar, we were no longer afraid that the BaseRender would become bloated with too many common elements between different types of cards, and we simply programmed the bar as needed for each type of card. The following is the partial division of BAR:

For the article card domain, we enumerate bars of all view types and divide them into bounding contexts by DDD, noting that the relationship between bar and bounding contexts is sufficient and unnecessary. Image loaders and video players (including automatic list playing) are common capabilities that some bars need to use. Meanwhile, decoupling the domain logic’s dependence on the image loading and video playing underlying libraries and encapsulating the underlying basic capabilities can become separate bounded contexts, which also conforms to the separation of concerns principle. Note that bar division is very flexible. It does not require continuous rectangular blocks on the interface, or certain conditions to be visible, or even UI visibility. For example, the following event consumption bar is only used to block touch events. Handle the whole card area of double click like dynamic effect, long press pop-up menu, click blank background jump to article details and other gestures.

Some bars have a certain degree of cooperation. For example, after entering a comment and publishing a comment, the newly published comment needs to be displayed in the comment display bar. After clicking the “favorites” operation in the operation bar, the collection success will be displayed and the feedback will be given to bar. Bar can be used as the aggregation root within each subdomain. Since the logic within BAR is similar, each subdomain can extend its own subdomain logic based on the aggregation root protocol ItemPartView. We use the simplest debugging information to display the DebugInfoBar (easy to view the position of the item in the current list) as an example. After omitting the attribute injection method, the main method is as follows:

Class DebugInfoBar: FrameLayout, ItemPartView {private var mItemPartLayoutMan: ItemPartLayoutMan<*, *, *>? = null / / utility class private var adapterContext: UnityAdapter. IAdapterContext? = null // ViewHolder private var mItemViewHolder: ItemViewHolder? Private var itemModel: PostCardModel? = null // findViewById stored in a bar instead of a ViewHolder private var hintTv: TextView? = null constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet? , defStyleAttr: Int) : super(context, attrs, defStyleAttr) init { View.inflate(context, R.layout.item_part_debug_info_bar, // RecyclerView onCreateViewHolder override fun onCreate() {// RecyclerView onCreateViewHolder override fun  onUpdate(itemModel: Any?) { itemModel ? : return visibility = View.GONE val position = mItemViewHolder? .itemPosition ? : -1 hintTv? Text = "I" in the first $${{position} position TrackerHelper. GetPostCard2StatisType (mItemViewHolder? .itemViewType!!) }" if (position == -1) { setBackgroundColor(Color.BLUE) } else { setBackgroundColor(if (position % 2 == 0) { Color.GREEN } else {color.red})} // RecyclerView onBindViewHolder override fun onUpdate(itemModel: Any? , payloads: List<Any>?) {} // restore the load when the image fails to load, which is an extension method, Override fun reloadImage() {} Override fun onFinishInflate() {super.onfinishinflate () hintTv = is automatically called by the DDD framework findViewById(R.id.hint_tv) } }Copy the code

At first glance, the onCreate and onUpdate methods are similar to the implementation of the divide-and-conquer phase, but they only receive the onCreateViewHolder and onBindViewHolder notifications of RecyclerView, but the way of thinking has undergone a subtle change. That is, we put the receipt of notifications into every bar of RecyclerView Item, so that the traditional View object is no longer an anaemic model object in the didivide and rule stage, but only a bare layout, and basic operations such as ID lookup, click event monitoring, data binding and rendering must be completed outside the View. These sliced views can do this by themselves.

How does this change engineering maintenance? If you take the logic of an ItemPartView operation outside of it, it ends up being messy, and finding and iterating can be extremely inconvenient. Take click event listening as an example. In the dial-and-conquer phase, using the BRVAH framework, it is common to write:

/ / create the Adapter (Adapter for BaseQuickAdapter type) Adapter. SetOnItemChildClickListener (object: OnItemChildClickListener { override fun onItemChildClick(adapter: BaseQuickAdapter, view: View, position: Int) { when (view.id) { R.id.item_view_1 -> { // firstly, get item data from view or view holder } R.id.item_view_2 -> { // firstly, Get item data from view or view holder}}}}) viewHolder.addOnClickListener(R.id.item_view_1, R.id.item_view_2);Copy the code

Create Adapter in Activity/Fragment, create item view in item Render, split click event registration and callback into two steps. Large lists have many types of items, and the layout of items is extremely complex. There are often dozens or hundreds of view ids, and there is no unified local management of their registration and callback, resulting in constant maintenance. In addition, the callback logic of the click event onItemChildClick is not in the code of the Item View that the developer is concerned about. When the project is huge, it is easy to cause the developer to omit. We have a case that we have modified the view exposure burying point parameter within the item but forgot to modify the click event burying point.

4.2 DDD governance

During the divide-and-conquer phase, ViewHolder attributes are often defined for each type. The attributes are used to hold references to findViewById. As more and more common elements are found in cards of different types, the number of attributes in the base ViewHolder will increase. Developers can get confused about which attributes correspond to which common elements, and even when attributes are defined in ViewHolder to hold data, which was a pain point in phase 2, (Although it is possible to avoid the ViewHolder problem by using Kotlinx’s synthetic tool and BRVAH’s baseViewholden.getView method, it is more cumbersome to write. More importantly, code readability is significantly reduced when a view uses multiple scenarios, the view ID uses other names, the view ID naming is not intuitive, the IDE layout tool automatically generates the view ID, and the ViewHolder attribute cannot be used to better name the element being manipulated.)

By setting click events in the bar, you can combine the registration and callback of click events into one step, logically consolidating the bar that the developer is concerned about. You can directly open properties in the bar to save the results of findViewById, without defining any specific ViewHolder. Eliminating the ability to add attributes to the ViewHolder completely, making modules more user-friendly for multiple maintainers.

class PostOperationBar : RelativeLayout, ItemPartView, View.OnClickListener, View.OnLongClickListener { private var mItemPartLayoutMan: ItemPartLayoutMan<*, *, *>? = null private var adapterContext: UnityAdapter.IAdapterContext? = null private var mItemViewHolder: ItemViewHolder? = null private var mItemModel: PostCardModel? = null private var likeBtn: View? = null // Like button private var Systemazel: View? = null // Recommended button private var subscribeBtn: View? = null // favorites button private var commentBtn: Viw? {view.inflate (context, r.layout.item_part_post_operation_bar, This)} override fun onCreate() {// Optimize the view element name, onUpdate and other methods to use these view elements more readability likeBtn = findViewById(r.i.btn_1); recommendBtn = findViewById(R.id.btn_2); subscribeBtn = findViewById(R.id.btn_3); commentBtn = findViewById(R.id.btn_4); subscribeBtn = findViewById(R.i.btn_5); / / bar Settings. Click likeBtn setOnClickListener (this) recommendBtn. SetOnClickListener (this) subscribeBtn. SetOnClickListener (this) CommentBtn. SetOnClickListener (this)/long/bar set by subscribeBtn setOnLongClickListener (this)} override fun onUpdate(itemModel: Any?) {// Use the more readable name likeBtn? .setSelected(mItemModel? .liked) recommendBtn? .setSelected(mItemModel? .recommended) subscribeBtn? .setSelected(mItemModel? .subscribed) ... } override fun onUpdate(itemModel: Any? , payloads: List<Any>?) {... } override fun reloadImage() { ... Override fun onClick(view: view) {when (view.id) {r.i.btn_1 -> {... } R.id.btn_2 -> { ... } R.id.btn_3 -> { ... } R.id.btn_4 -> { ... } R.id.btn_5 -> { ... Override fun onLongClick(view: view): Boolean {when (view.id) {r.i.btn_5 -> {override fun onLongClick(view: view): Boolean {when (view.id) {R.I.B. }}}}Copy the code

Through the division of bounded context, we have taken the first step in DDD governance. We have enabled BAR to complete a series of processes such as ID search, click event monitoring, data binding and rendering. ItemPartView has become the object of the congestion model. It’s not enough just to be the subject of this congestion model, Imagine list video scrolls off the screen to stop play scene, rolling stop within the automatic selection screen to show the video playback scenarios, page into the background to stop playing video scene, listening user trigger the likes of this article and the other page like the state of the scene, synchronous update the current list page bar additional resources released when destroyed, These common application scenarios go beyond the existing capabilities of ItemPartView and break the established conventions of DDD governance. In order to keep the project from degrading, we need to introduce another aggregate root of BAR, ItemPartLayoutMan. Enables ItemPartView to listen for notifications about page life cycle, onActivityResult, scroll status, etc.

Taking user information bar as an example, we need to process the dynamic update of user concern status and collection information on the bar in the list. We can register and destroy broadcast in the life cycle of ItemPartLayoutMan. For example, when receiving the broadcast of concerned user, we need to obtain the item containing the user’s profile picture in the list. Finally call the notifyItemChanged of the RecyclerView Adapter to send local updates to the bar:

class PostOwnerItemPartLayoutMan() : ItemPartLayoutMan<PostOwnerBar, PostCardModel, PostCardModel>() {private val followUserReceiver = object: BroadcastReceiver() { val actionUserId: Long = ... val isFollowAction: Boolean = ... adapterContext? .adapter? .data? .forEachIndexed { position, item -> item? .model? .let { model -> if (model.userId == actionUserId) { adapterContext.adapter.notifyItemChanged(position, if (isFollowAction) { PayLoadType.PAYLOAD_FOLLOW_ACTION } else { PayLoadType.PAYLOAD_CANCEL_FOLLOW_ACTION }) } } } } // Update articles were added to inform private collection val updateCollectionInfoReceiver = object: BroadcastReceiver () {... } override fun onContextAttached() { adapterContext.registerLocalBroadcastReceiver(BroadCastHelper.FOLLOW_FILTER, followUserReceiver) adapterContext.registerLocalBroadcastReceiver(CollectionConstant.ActionKey.ACTION_COLLECT_FILTER, updateCollectionInfoReceiver) } override fun getItemPartModel(itemModel: PostCardModel?) : PostCardModel? { return itemModel } override fun onDestroy(owner: LifecycleOwner) { adapterContext.unregisterLocalBroadcastReceiver(followUserReceiver) AdapterContext. UnregisterLocalBroadcastReceiver (updateCollectionInfoReceiver)} / / notify the region the behavior of the UI updates, update when they check the model data, Model Data set by update initiator enum class PayLoadType {// Concern PAYLOAD_FOLLOW_ACTION, // Cancel concern PAYLOAD_CANCEL_FOLLOW_ACTION, PAYLOAD_UPDATE_COLLECTION_INFO_ACTION}}Copy the code

Receive local update notification in user info bar in onUpdate method and update interface:

class PostOwnerBar : FrameLayout, ItemPartView, View.OnClickListener { ... override fun onUpdate(itemModel: Any? , payloads: List<Any>?) { payloads? .let { list -> list.forEach { one -> when (one) { PostOwnerItemPartLayoutMan.PayLoadType.PAYLOAD_FOLLOW_ACTION -> { // Button is set to have attention} PostOwnerItemPartLayoutMan. PayLoadType. PAYLOAD_CANCEL_FOLLOW_ACTION - > {/ / button to restore did not focus on}}}}}Copy the code

Take the video playing scene in the list as an example. PostVideoPlayBar is a video playing bar, and we can process the interactive logic of scrolling and stopping playing in its corresponding ItemPartLayoutMan:

class PostVideoPlayItemPartLayoutMan() : ItemPartLayoutMan<PostVideoPlayBar, PostCardModel, PostCardModel>() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {// Handle rollout screen stop play} Override fun onScrollStateChanged(RecyclerView: RecyclerView, newState: Int) {override fun onStop(LifecycleOwner owner) {override fun onStop(LifecycleOwner owner) {override fun onStop(LifecycleOwner owner) {Copy the code

The domain inevitably involves the cache of some intermediate data, such as parsed rich text data, exposed AD ID, default avatar drawable, etc. In the divide-and-conquer stage, the data will be cached in RecyclerView Adapter Controller. In the DDD phase, each bounding context can have its own data cache management class, although it is not necessary to define a different type for each bounding context:

// Context cache management class PostCardCache: IAdapterCache {val exposedAdIds = HashSet<String>() override fun clear() {exposedadid.clear ()}}Copy the code

Each bounded context has access to the desired data cache through the domain utility class adapterContext:

Funtrackadexpose () {val adInfo = mItemModel? .adInfo ? : return adapterContext? .cache? .getItemCache(PostCardCache::class.java)? .let { cache -> if (! cache.exposedAdIds.contains(adInfo.id)) { adInfo.addShow() cache.exposedAdIds.add(adInfo.id) } } }Copy the code

Domain utility classes also provide the ability to invoke other contextual services, which can be easily invoked by other bars simply by declaring their service interfaces. Let’s take the display of the Collection prompt bar as an example (the collection prompt bar appears at the bottom of the content when clicking the “Collection” button). The collection prompt bar defines the service interface to display itself:

interface ISubscribeToastItemService : ItemService {
    fun showWithAlphaAnim(folderName: String?)
}
Copy the code

Bar realizes service interface:

class SubscribeToastBar : FrameLayout, ItemPartView, ISubscribeToastItemService { override fun showWithAlphaAnim(folderName: String?) {// Find the anchor point of the content view according to the different item type and adjust its position}}Copy the code

We only need to click on the button in the action bar bar collection event, call adapterContext. ItemServiceProvider. GetItemService method, the incoming ISubscribeToastItemService service interface class, You can easily call the service of the favorite prompt bar:

class PostOperationBar : RelativeLayout, ItemPartView, View.OnClickListener, View.OnLongClickListener { ... Override fun onClick(view: view) {override fun onClick(view: view) { R.i.btn_5 -> {doRequest {success -> if (success) {adapterContext? .itemServiceProvider!! .getItemService(ISubscribeToastItemService::class.java, itemViewHolder)? .showWithAlphaAnim(folderName) } } } } } }Copy the code

4.3 DDD architecture

After DDD governance, the list architecture becomes clearer. From a page perspective, the complete DDD architecture is as follows:

Because the team members are familiar with BRVAH, we designed a DDD Addapter Framework on top of BRVAH to support the most basic domain logic. Each item type really shows the bar, which is arranged by ItemLayoutMan. Each ItemLayoutMan corresponds to an XML file, which can be used to define the layout of item in the way of arranging bar in XML. Bar itself is a ViewGroup and also a domain service. When the page is initialized, we simply register the item type and ItemLayoutMan collection that the page cares about. The type that the page cares about is usually related to the data that the interface returns. Let’s take PostVideoItemLayoutMan, which represents a video type card, and see how it orchestrates a bar. Register the bar you want to orchestrate:

class PostVideoItemLayoutMan() : ItemLayoutMan<ItemViewHolder, PostCardModel>() {

    override fun onContextAttached() {
        registerSimpleItemPartView(TopStubBar::class.java, BottomStubBar::class.java, DebugInfoBar::class.java, RecommendWordsBar::class.java, 
                                   PostAuditBar::class.java, PostReadNumBar::class.java, PostTextBodyBar::class.java, PostTagsBar::class.java,
                                   DoubleClickBar::class.java, SubscribeToastBar::class.java, PostTopHintBar::class.java)
        registerItemPartLayoutMan(PostOwnerItemPartLayoutMan::class.java, PostVideoPlayItemPartLayoutMan::class.java, 
                                  PostOperationItemPartLayoutMan::class.java, PostCommentListItemPartLayoutMan::class.java,
                                  PostCommentInputItemPartLayoutMan::class.java)
    }

}
Copy the code

It then arranges and lays it out in XML. Before DDD governance, the item layout of the list was very large, equivalent to the layout of each bar tiled under the item root layout, and the nesting level was very deep. Although some layouts had include extraction, it was standard but not mandatory, resulting in the mix of include and tiling. After DDD governance, The XML file for each item is easy to maintain and has only one level of hierarchy:

<? The XML version = "1.0" encoding = "utf-8"? > <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/lofter_background_primary"> <lofter.component.middle.business.postCard2.common.viewstub.TopStubBar ... /> <lofter.component.middle.business.postCard2.common.debug.DebugInfoBar ... /> <lofter.component.middle.business.postCard2.common.audit.PostAuditBar ... /> <lofter.component.middle.business.postCard2.common.owner.user.PostOwnerBar ... /> <lofter.component.middle.business.postCard2.text.PostTextBodyBar ... /> <lofter.component.middle.business.postCard2.video.PostVideoPlayBar ... /> <lofter.component.middle.business.postCard2.common.side.PostReadNumBar ... /> <lofter.component.middle.business.postCard2.common.operation.PostOperationBar ... /> <lofter.component.middle.business.postCard2.common.viewstub.BottomStubBar ... /> <lofter.component.middle.business.postCard2.common.toast.subscribe.SubscribeToastBar android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" android:layout_marginLeft="@dimen/post_card_content_left_margin" android:layout_marginRight="@dimen/post_card_content_right_margin" android:visibility="gone"/> ... <lofter.component.middle.business.postCard2.common.doubleClickLike.DoubleClickBar ... /> </androidx.constraintlayout.widget.ConstraintLayout>Copy the code

ItemLayoutMan = ItemLayoutMan = ItemLayoutMan = ItemLayoutMan = ItemLayoutMan = ItemLayoutMan

adapter = UnityAdapter(this, ArrayList<UnityItemEntity>()).apply { registerItemLayoutMan(UnityViewType.POST_CARD_TEXT, R.layout.post_card_item_text, PostTextItemLayoutMan: : class. Java) / / text type item registerItemLayoutMan (UnityViewType POST_CARD_PHOTO, R.layout.post_card_item_photo, PostPhotoItemLayoutMan: : class. Java) / / picture type item registerItemLayoutMan (UnityViewType POST_CARD_MUSIC, R.layout.post_card_item_music, PostMusicItemLayoutMan: : class. Java) / / music type item registerItemLayoutMan (UnityViewType POST_CARD_VIDEO, R.l ayout. Post_card_item_video PostVideoItemLayoutMan: : class. Java) / / video type item... }Copy the code

Let’s take a look at the engineering structure after DDD governance. Since the number of bars is very large, we cluster the bounded context. First, we store the bar specific to each item type into the corresponding type directory:

Common elements such as action bar and user information hold the common directory. For example, the common directory stores the event consumption bar and the action bar:

In bar directory, only the aggregation root and context service must be placed under the level 1 directory, and other directories can be stored by themselves. For example, the click event logic in the operation bar is complicated and involves many analogies. You can create a click directory in the operation directory to store it.

4.4 anticorrosion

In DDD, preservative layers are introduced between contexts to prevent them from being eroded by other contexts. And in the field of large client list, a list of render data from the back-end data conversion, list style tend to reuse in more than one page, each page request different back-end services, in addition, different context need only part of the whole item data, or by the intermediate processing into bar view rendering data, so we need to give field introduced anticorrosion layer:

4.5 DDD frameworks

Large list governance is a difficult process, we completed the article card list governance in the version of the article card revision, in order to ensure that the governance experience can be copied, we designed a business-independent DDD Adapter Framework, new large list can be directly based on the DDD Framework development. In addition to the above points, the DDD framework supports the use of more scenarios, such as singleton Adapters as a special item type, so that the card styles of older singleton adapters can be quickly migrated to DDD Adapters for distribution. In addition to managing large lists, the goal of the DDD framework is to unify all lists throughout the application, to avoid situations where certain item types cannot be displayed in other lists, to unify all item-type card pools, and to provide on-demand services like Tinker Bells when any page requires list rendering. For example, in addition to article cards, we have used the DDD framework in the areas of large video cards and complex lists of video series pop-ups:

5 end

Since large lists are often maintained by more than one or two people, the DDD model needs to be adopted by the promoters in addition to sharing within the group and incorporating suggestions from other members. For example, some people put forward that it is a little tedious to define an ItemPartLayoutMan for each bar. Some bars are relatively simple and do not need to listen for page life cycle and scrolling events, or for notifications of other pages for dynamic updates. Therefore, these problems need to be improved. Provides registerSimpleItemPartView method, which can directly add the bar, no longer need ItemPartLayoutMan, the incremental improvements are the framework of grinding. After governance, the article card has been maintained by more people and can be co-developed. The development efficiency is higher when there is interaction and UI adjustment in version iteration, and the test impact of development and modification is smaller. Through code review, no online bug has occurred so far.

Author: Fan Chencan, Technical Group, LOFTER

This article is published by the front-end team of NetEase Yuanqi Business Division. Any unauthorized reprinting of this article is prohibited. Welcome to share with us the technical problems and experience related to the front-end. Meanwhile, the team and department are recruiting developers for front-end, server and client positions. You can contact [email protected] for the above information.