浅谈基于prism的软件系统的架构设计

Hello寻梦者! Hello寻梦者!     2022-09-22     704

关键词:

  很早就想写这么一篇文章来对近几年使用Prism框架来设计软件来做一次深入的分析了,但直到最近才开始整理,说到软件系统的设计这里面有太多的学问,只有经过大量的探索才能够设计出好的软件产品,就本人的理解,一个好的软件必须有良好的设计,这其中包括:易阅读、易扩展、低耦合、模块化等等,如果你想设计一个好的系统当然还要考虑更多的方面,本篇文章主要是基于微软的Prism框架来谈一谈构建模块化、热插拔软件系统的思路,这些都是经过大量项目实战的结果,希望能够通过这篇文章的梳理能够对构建软件系统有更加深刻的理解。

     首先要简单介绍一下Prism这个框架:Prism框架通过功能模块化的思想,将复杂的业务功能和UI耦合性进行分离,通过模块化,来最大限度的降低耦合性,很适合我们进行类似插件化的思想来组织系统功能,并且模块之间,通过发布和订阅事件来完成信息的通信,而且其开放性支持多种框架集成。通过这些简单的介绍就能够对此有一个简单的理解,这里面加入了两种依赖注入容器,即:Unity和MEF两种容器,在使用的时候我们首先需要确定使用何种容器,这个是第一步。第二步就是如何构建一个成熟的模块化软件,这个部分需要我们能够对整个软件系统功能上有一个合理的拆分,只有真正地完全理解整个系统才能够合理抽象Module,然后降低Module之间的耦合性。第三步就是关于模块之间是如何进行通讯的,这一部分也是非常重要的部分,今天这篇文章就以Prism的Unity依赖注入容器为例来说明如何构建模块化软件系统,同时也简要说明一下软件系统的构建思路。

  这里以百度地图为例来说一下如果使用WPF+Prism的框架来设计的话,该怎样来设计,当然这里只是举一个例子,当然这篇文章不会就里面具体的代码的逻辑来进行分析,事实上我们也不清楚这个里面具体的内部实现,这里仅仅是个人的观点。

图一 百度地图主界面

  注意下面所有的代码并非和上面的截图一致,截图仅供参考

  如图一所示,整个界面从功能主体上区分的话,就能够很好的分成这几个部分,左侧是一个搜索区域,右边是两个功能区和一个个人信息区域,中间是地图区域,这个是我们在看完这个地图之后第一眼就能想到的使用Prism能够构建的几个模块(Modules)。在定完整个系统可以分为哪几个模块之后我们紧接着就要分析每一个模块包含哪些功能,并根据这些功能能够定义哪些接口,我们可以新建一个类库,专门用于定义整个应用程序的接口,并放在单独的类库中,比如左侧的地图搜索区域我们可以定义一个IMapSearch的接口,用于定于这个部分有哪些具体的功能,如下面代码所示。  

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace IGIS.SDK { public delegate List<Models.SearchResult> OnMapSearchHandle(string keyword); public interface IMapSearch { void AddSearchListener(string type, OnMapSearchHandle handle); void RemoveSearchListener(string type); void ShowResults(List<Models.SearchResult> results); void ClearResults(); System.Collections.ObjectModel.ObservableCollection<Models.SearchResult> GetAllResults(); event EventHandler<string> OnSearchCompleted; event EventHandler<System.Collections.ObjectModel.ObservableCollection<Models.SearchResult>> OnClearSearchResult; event EventHandler<System.Collections.ObjectModel.ObservableCollection<Models.SearchResult>> OnExecuteMultiSelected; void ShowFloatPanel(Models.SearchResult targetResult, FrameworkElement ui); } }

  这是第一步,为左侧的搜索区域定义好接口,当然模块化的设计必然包括界面和界面抽象,即WPF中的View层和ViewModel层以及Model层,我们可以单独新建一个项目(自定义控件库为佳)来单独实现这一部分的MVVM,然后生成单独的DLL供主程序去调用,比如新建一个自定义空间库命名为Map.SearchModule,然后分别设计这几个部分,这里列出部分代码仅供参考。

<UserControl x:Class="IGIS.MapSearch"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" Title="IGIS"
             xmlns:cvt="clr-namespace:IGIS.Utils" xmlns:gisui="clr-namespace:IGIS.UI;assembly=IGIS.UI"
             xmlns:region="http://www.codeplex.com/CompositeWPF"
             xmlns:ui="clr-namespace:X.UI;assembly=X.UI"
             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
             d:DesignHeight="600" d:DesignWidth="1100">
   
   <Grid> 
      ......
   </Grid>
</UserControl>  

  当然最重要的部分代码都是在ViewModel层中去实现的,这个层必须要继承自IMapSearch这个接口,然后和View层通过DataContext绑定到一起,这样一个完整的模块化的雏形就出来了,后面还有几个重要的部分再一一讲述。

using IGIS.SDK.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Collections.ObjectModel;
using X;
using X.Infrastructure;

namespace IGIS.ViewModels
{
    class SearchManager : X.Infrastructure.VMBase, IGIS.SDK.IMapSearch
    {
        public SearchManager()
        {
            Search = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoSearch);
            ClearResult = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoClearResult);
            ShowSelected = new Microsoft.Practices.Prism.Commands.DelegateCommand(DoShowSelected);
            Listeners.Add(new Listener { Name = "全部", Handle = null });
        }

        private void DoShowSelected()
        {
            if (null != OnExecuteMultiSelected)
            {
                System.Collections.ObjectModel.ObservableCollection<SearchResult> selected = new ObservableCollection<SearchResult>();
                foreach (var itm in SelectedItems)
                {
                    if (itm is SearchResult)
                        selected.Add(itm as SearchResult);
                }
                OnExecuteMultiSelected(this, selected);
            }
        }

        private static SearchManager _instance;

        public static SearchManager Instance
        {
            get
            {
                if (null == _instance)
                    _instance = new SearchManager();
                return _instance;
            }
            set { _instance = value; }
        }

        private void DoSearch()
        {
            ClearResults();
            foreach (var ls in Listeners)
            {
                if (string.IsNullOrEmpty(SelectedType) || SelectedType == "全部" || SelectedType == ls.Name)
                    if (ls.Handle != null)
                    {
                        List<SearchResult> res = null;
                        Application.Current.Dispatcher.Invoke(new Action(() =>
                        {
                            res = ls.Handle.Invoke(Keyword);
                        }), System.Windows.Threading.DispatcherPriority.Normal);
                        if (null != res && res.Count > 0)
                        {
                            foreach (var itm in res)
                            {
                                Application.Current.Dispatcher.Invoke(new Action(() =>
                                {
                                    Results.Add(itm);
                                }));
                            }
                        }
                    }
            }

            if (null != OnSearchCompleted)
                OnSearchCompleted(Results, Keyword);

            DoRemoteSearch(SelectedType, Keyword);
        }

        private string _keyword;

        public string Keyword
        {
            get { return _keyword; }
            set
            {
                if (_keyword != value)
                {
                    _keyword = value;
                    OnPropertyChanged("Keyword");
                }
            }
        }

        private string _selectedType = "全部";

        public string SelectedType
        {
            get { return _selectedType; }
            set
            {
                if (_selectedType != value)
                {
                    _selectedType = value;
                    OnPropertyChanged("SelectedType");
                }
            }
        }

        private ICommand _showSelected;

        public ICommand ShowSelected
        {
            get { return _showSelected; }
            set { _showSelected = value; }
        }

        private ICommand _search;

        public ICommand Search
        {
            get { return _search; }
            set
            {
                if (_search != value)
                {
                    _search = value;
                    OnPropertyChanged("Search");
                }
            }
        }

        private ICommand _ClearResult;

        public ICommand ClearResult
        {
            get { return _ClearResult; }
            set { _ClearResult = value; }
        }

        private void DoClearResult()
        {
            ClearResults();
        }

        private System.Collections.ObjectModel.ObservableCollection<SearchResult> _results
            = new System.Collections.ObjectModel.ObservableCollection<SearchResult>();

        public System.Collections.ObjectModel.ObservableCollection<SearchResult> Results
        {
            get { return _results; }
            set
            {
                if (_results != value)
                {
                    _results = value;
                    OnPropertyChanged("Results");
                }
            }
        }

        private System.Collections.IList _selectedItems;

        public System.Collections.IList SelectedItems
        {
            get { return _selectedItems; }
            set { _selectedItems = value; }
        }

        #region SDK

        public class Listener : X.Infrastructure.NotifyObject
        {
            private string _name;

            public string Name
            {
                get { return _name; }
                set
                {
                    if (_name != value)
                    {
                        _name = value;
                        OnPropertyChanged("Name");
                    }
                }
            }

            private SDK.OnMapSearchHandle _handle;

            public SDK.OnMapSearchHandle Handle
            {
                get { return _handle; }
                set { _handle = value; }
            }

        }

        public event EventHandler<string> OnSearchCompleted;
        public event EventHandler<System.Collections.ObjectModel.ObservableCollection<SDK.Models.SearchResult>> OnClearSearchResult;
        public event EventHandler<ObservableCollection<SearchResult>> OnExecuteMultiSelected;

        private System.Collections.ObjectModel.ObservableCollection<Listener> _listeners
            = new System.Collections.ObjectModel.ObservableCollection<Listener>();

        public System.Collections.ObjectModel.ObservableCollection<Listener> Listeners
        {
            get { return _listeners; }
            set
            {
                if (_listeners != value)
                {
                    _listeners = value;
                    OnPropertyChanged("Listeners");
                }
            }
        }

        public System.Collections.ObjectModel.ObservableCollection<SearchResult> GetAllResults()
        {
            return Results;
        }

        public void AddSearchListener(string type, SDK.OnMapSearchHandle handle)
        {
            Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                var itm = Listeners.Where(x => x.Name == type).SingleOrDefault() ?? null;
                if (null == itm)
                {
                    itm = new Listener() { Name = type };
                    Listeners.Add(itm);
                }
                itm.Handle = handle;
            }));
        }

        public void RemoveSearchListener(string type)
        {
            Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                try
                {
                    var itm = Listeners.Where(x => x.Name == type).SingleOrDefault() ?? null;
                    if (null != itm)
                    {
                        Listeners.Remove(itm);
                    }
                }
                catch (Exception)
                {
                }
            }));
        }

        public void ShowResults(List<SearchResult> results)
        {
            ClearResults();
            foreach (var itm in results)
            {
                Application.Current.Dispatcher.Invoke(new Action(() =>
                {
                    Results.Add(itm);
                }));
            }
        }

        public void ClearResults()
        {
            Application.Current.Dispatcher.Invoke(new Action(() =>
            {
                if (null != OnClearSearchResult && Results.Count > 0)
                    OnClearSearchResult(this, Results);
                Results.Clear();
                ClearRemoteResults();
            }));
        }

        public void ShowFloatPanel(SearchResult targetResult, FrameworkElement ui)
        {
            if (null != OnShowFloatPanel)
                OnShowFloatPanel(targetResult, ui);
        }

        internal event EventHandler<FrameworkElement> OnShowFloatPanel;

        #endregion

        #region 大屏端同步命令

        void DoRemoteSearch(string type, string keyword)
        {
            X.Factory.GetSDKInstance<X.IDataExchange>().Send(new IGIS.SDK.Messages.RemoteMapSearchMessage() { SelectedType = this.SelectedType, Keyword = this.Keyword }, "IGISMapSearch");
        }

        void ClearRemoteResults()
        {
            X.Factory.GetSDKInstance<X.IDataExchange>().Send(new X.Messages.MessageBase(), "IGISClearMapSearch");
        }

        #endregion

    }
}

  如果熟悉Prism的开发者肯定知道这部分可以完整的定义为一个Region,在完成这部分之后,最重要的部分就是将当前的实现接口IGIS.SDK.IMapSearch的对象注入到UnityContainer中从而在其他的Module中去调用,这样就能够实现不同的模块之间进行通信,具体注入的方法请参考下面的代码。 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Practices.Unity;
using X;

namespace IGIS
{
    public class IGISProductInfo : IModule
    {
        Microsoft.Practices.Prism.Regions.IRegionViewRegistry m_RegionViewRegistry;
        public IGISProductInfo(Microsoft.Practices.Unity.IUnityContainer container)
        {
            m_RegionViewRegistry = _RegionViewRegistry; 
            container.RegisterInstance<IGIS.SDK.IMapSearch>(ViewModels.SearchManager.Instance);            
        } 
        
        public void Initialize()
        {
            m_RegionViewRegistry.RegisterViewWithRegion(“MapSearchRegion”, typeof(Views.IGIS.MapSearch)); 
        } 
    }
}

    首先我们通过m_RegionViewRegistry.RegisterViewWithRegion(“MapSearchRegion”, typeof(Views.IGIS.MapSearch))来将当前的View注册到主程序的Shell中,在主程序中我们只需要通过<ContentControl region:RegionManager.RegionName="MapSearchRegion"></ContentControl>就能够将当前的View放到主程序的中,从而作为主程序的界面的一部分,然后通过代码:container.RegisterInstance<IGIS.SDK.IMapSearch>(ViewModels.SearchManager.Instance),就能够将当前实现IMapSearch的接口的实例注入到Prism框架的全局的UnityContainer中,最后一步也是最关键的就是在其它的模块中,如果我们需要调用当前实现IMapSearch的接口的方法,那该怎么来获取到实现这个接口的实例呢?

  下面的代码提供了两个方法,一个同步方法和一个异步的方法来获取当前的实例,比如使用同步的方法,我们调用GetSDKInstance这个方法传入类型:IGIS.SDK.IMapSearch时就能够获取到注入到容器中的唯一实例:ViewModels.SearchManager.Instance,这样我们就能够获取到这个实例了。

 public static T GetSDKInstance<T>() where T : class
        {
            if (currentInstances.ContainsKey(typeof(T)))
                return currentInstances[typeof(T)] as T;
            try
            {
                var instance = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<T>();
                currentInstances[typeof(T)] = instance;
                return instance;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Trace.TraceError(ex.ToString());
                return null;
            }
        }

        private static object Locker = new object();
        public static void GetSDKInstanceAysnc<T>(Action<T> successAction) where T : class
        {
            if (currentInstances.ContainsKey(typeof(T)))
            {
                successAction.Invoke(currentInstances[typeof(T)] as T);
                return;
            }
            Task.Factory.StartNew(new Action(() =>
            {
                lock (Locker)
                {
                    T instance = null;
                    int tryCount = 0;
                    while (instance == null && tryCount <= 100)
                    {
                        tryCount++;
                        try
                        {
                            instance = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<T>();
                        }
                        catch
                        {
                        }
                        if (null != instance)
                        {
                            currentInstances[typeof(T)] = instance;
                            successAction.Invoke(instance);
                            return;
                        }
                        else
                        {
                            System.Threading.Thread.Sleep(50);
                        }
                    }
                }
            }));
        }

  在看完上面的介绍之后我们似乎对基于Prism的模块化开发思路有了一定的理解了,但是这些模块是在何时进行加载的呢?Prism框架是一种预加载模式,即生成的每一个Module在主程序Shell初始化的时候就会去加载每一个继承自IModule的接口的模块,当然这些模块是分散在程序的不同目录中的,在加载的时候需要为其指定具体的目录,这样在主程序启动时就会加载不同的模块,然后每个模块加载时又会将继承自特定接口的实例注册到一个全局的容器中从而供不同的模块之间相互调用,从而实现模块之间的相互调用,同理图一中的功能区、个人信息区、地图区都能够通过继承自IModule接口来实现Prism框架的统一管理,这样整个软件就可以分成不同的模块,从而彼此独立最终构成一个复杂的系统,当然这篇文章只是做一个大概的分析,为对Prism框架有一定理解的开发者可以有一个指导思想,如果想深入了解Prism的思想还是得通过官方的参考代码去一点点理解其指导思想,同时如果需要对Prism有更多的理解,也可以参考我之前的博客,本人也将一步步完善这个系列。

      最后我们要看看主程序如何在初始化的时候来加载这些不同的模块的dll的,请参考下面的代码:

using System;
using System.Windows;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Prism.UnityExtensions;
using Microsoft.Practices.Prism.Logging;

namespace Dvap.Shell.CodeBase.Prism
{
   public class DvapBootstrapper : Microsoft.Practices.Prism.UnityExtensions.UnityBootstrapper
    {
        private readonly string[] m_PluginsFolder=new string[3] { "FunctionModules", "DirectoryModules", "Apps"};
        private readonly CallbackLogger m_callbackLogger = new CallbackLogger();
        #region Override
        /// <summary>
        /// 创建唯一的Shell对象
        /// </summary>
        /// <returns></returns>
        protected override DependencyObject CreateShell()
        {
           return this.Container.TryResolve<Dvap.Shell.Shell>();    
        }

        protected override void InitializeShell()
        {
            base.InitializeShell();
            Application.Current.MainWindow = (Window)this.Shell;
            Application.Current.MainWindow.Show();
        }

        /// <summary>
        /// 创建唯一的Module的清单
        /// </summary>
        /// <returns></returns>
        protected override IModuleCatalog CreateModuleCatalog()
        {
            return new CodeBase.Prism.ModuleCatalogCollection();  
        }

        /// <summary>
        /// 配置唯一的ModuleCatalog,这里我们通过从特定的路径下加载
        /// dll
        /// </summary>
        protected override void ConfigureModuleCatalog()
        {
            try
            {
                var catalog = ((CodeBase.Prism.ModuleCatalogCollection)ModuleCatalog);

                foreach (var pluginFolder in m_PluginsFolder)
                {
                    if (pluginFolder.Contains("~"))
                    {
                        DirectoryModuleCatalog catApp = new DirectoryModuleCatalog() { ModulePath = pluginFolder.Replace("~", AppDomain.CurrentDomain.BaseDirectory) };
                        catalog.AddCatalog(catApp);
                    }
                    else
                    {
                        if (!System.IO.Directory.Exists(@".\" + pluginFolder))
                        {
                            System.IO.Directory.CreateDirectory(@".\" + pluginFolder);
                        }

                        foreach (string dic in System.IO.Directory.GetDirectories(@".\" + pluginFolder))
                        {
                            DirectoryModuleCatalog catApp = new DirectoryModuleCatalog() { ModulePath = dic };
                            catalog.AddCatalog(catApp);
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            } 
        }

        protected override ILoggerFacade CreateLogger()
        {
            return this.m_callbackLogger; 
        }
        #endregion

    }

}

  看到没有每一个宿主应用程序都有一个继承自Microsoft.Practices.Prism.UnityExtensions.UnityBootstrapper的类,我们需要重写其中的一些方法来实现Prism程序的模块加载,例如重写 override void ConfigureModuleCatalog() 我们的宿主程序就知道去哪里加载这些继承自IModule的dll,还有必须重载CreateShell和InitializeShell()

这些基类的方法来制定主程序的Window,有了这些我们就能够构造一个完整的Prism程序了,对了还差最后一步,就是启动Prism的Bootstrapper,我们一般是在WPF程序的App.xaml.cs中启动这个,例如: 

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace Dvap.Shell
{
    /// <summary>
    /// App.xaml 的交互逻辑
    /// </summary>
    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            new CodeBase.Prism.DvapBootstrapper().Run();
            this.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
                    
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            try
            {
                if (e.ExceptionObject is System.Exception)
                {
                    WriteLogMessage((System.Exception)e.ExceptionObject);
                }
            }
            catch (Exception ex)
            {
                WriteLogMessage(ex);
            }
        }

        private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            try
            {
                WriteLogMessage(e.Exception);
                e.Handled = true;
            }
            catch (Exception ex)
            {
                WriteLogMessage(ex);
            }
        }

        public static void WriteLogMessage(Exception ex)
        {
            //如果不存在则创建日志文件夹
            if (!System.IO.Directory.Exists("Log"))
            {
                System.IO.Directory.CreateDirectory("Log");
            }
            DateTime now = DateTime.Now;
            string logpath = string.Format(@"Log\Error_{0}{1}{2}.log", now.Year, now.Month, now.Day);
            System.IO.File.AppendAllText(logpath, string.Format("\r\n************************************{0}*********************************\r\n", now.ToString("yyyy-MM-dd HH:mm:ss")));
            System.IO.File.AppendAllText(logpath, ex.Message);
            System.IO.File.AppendAllText(logpath, "\r\n");
            System.IO.File.AppendAllText(logpath, ex.StackTrace);
            System.IO.File.AppendAllText(logpath, "\r\n");
            System.IO.File.AppendAllText(logpath, "\r\n*************************************************r\n");
        }
    }
}

  在应用程序启动时调用 new CodeBase.Prism.DvapBootstrapper().Run()启动Prism应用程序,从而完成整个过程,当然上面的讲解只能够说明Prism的冰山一角,了解好这个框架将为我们开发复杂的应用程序提供一种新的思路。

 

浅谈一下可扩展性网站架构设计

扩展性(Extensibility)-指对现有系统影响最小的情况下,系统功能可持续扩展或者提升的能力,表现在系统基础设施稳定不需要经常变更,应用之间较少依赖和耦合,对需求变更可以敏捷响应。它是系统架构设计层面的开闭原则... 查看详情

浅谈数据仓库架构设计

1.数据中台与DW/BI/DSS个人认为数据中台本质上是一种新的适配大数据技术发展的新的“数据仓库-决策支持(商业智能)”架构。这个架构是构建在传统的架构基础之上,对传统架构的一种新的发展。数据中台从企业的... 查看详情

转--《浅谈12306核心模型设计思路和架构设计》

12306这个系统,核心要解决的问题是网上售票。涉及到2个角色使用该系统:用户、铁道部。用户的核心诉求是查询余票、购票;铁道部的核心诉求是售票。购票和售票其实是一个场景,对用户来说是购票,对铁道部来说是售票。... 查看详情

ui设计设计题目

...面我为大家介绍ui设计设计题目,希望能帮到大家!  1.浅谈UI设计中的视觉设计风格发展  2.浅谈UI设计中的视觉表现  3.UI交互智能迷你净化器设计--创意思维设计  4.回合制手游新增UI设计规范研究  5.基于用户体验的... 查看详情

软考系统架构设计师软件架构设计④基于架构的软件开发方法

...计架构文档化架构复审架构实现架构演化练习题基本概念基于架构的软件设计(Architecture-BasedSoftwareDesign,ABSD)方法是架构驱动的,即强调由业务、质量和功能需求的组合驱动架构设。ABSD方法有以下三个基础:功能的分解,ABSD... 查看详情

基于wpf模块化架构下的本地化设计实践(代码片段)

原文:基于WPF模块化架构下的本地化设计实践背景描述#最近接到一个需求,就是要求我们的WPF客户端具备本地化功能,实现中英文多语言界面。刚开始接到这个需求,其实我内心是拒绝的的,但是没办法,需求是永无止境的。所... 查看详情

基于dolphinscheduler的使用浅谈数仓分层及模型设计

前言:本文旨在简单介绍DS的概述和架构上的设计,对其安装等不做展开介绍。之前了解了一下,很多小伙伴也在使用该产品。我呢,也是到现在公司后才开始接触并使用,对其“开发”的还不够深,这里... 查看详情

基于dolphinscheduler的使用浅谈数仓分层及模型设计

前言:本文旨在简单介绍DS的概述和架构上的设计,对其安装等不做展开介绍。之前了解了一下,很多小伙伴也在使用该产品。我呢,也是到现在公司后才开始接触并使用,对其“开发”的还不够深,这里... 查看详情

浅谈分布式全闪存储自动化测试平台设计

摘要本文简单介绍了分布式全闪的基本架构,根据对存储架构的理解和软件自动化测试系统理论的研究,指出软件自动化系统需涉及的主要方面,为软件自动化测试系统实现奠定基础。根据软件测试需求(功能、... 查看详情

浅谈高并发架构

   本篇文章主要是浅谈一些高并发的方案,指出一个大致方向,如果有需要优化提高系统性能,可以从以下方法中找出合适的使用。   随着淘宝、京东、唯品会等很多电商的出现,所谓互联网公司也就经常... 查看详情

基于go语言实现高并发推荐系统架构设计

本文由InfoQ整理自小年糕算法中台后端架构师封幼林在QCon+大厂案例(2021冬季北京站)的分享《高并发推荐系统架构设计》。  作者|封幼林 编辑|贾亚宁 你好!我是封幼林,在小年糕负责推荐系统,主要从事服务架构... 查看详情

(软考笔记)——系统架构设计师-软件架构设计笔记

...念架构的定义软件架构设计与生命周期软件架构的重要性基于架构的软件开发方法体系结构的设计方法概述概念和术语基于体系结构的开发模型体系结构需求体系结构设计体系结构文档化体系结构复审体系结构的实现体系结构的... 查看详情

基于dolphinscheduler的使用浅谈数仓分层及模型设计

前言:本文旨在简单介绍DS的概述和架构上的设计,对其安装等不做展开介绍。之前了解了一下,很多小伙伴也在使用该产品。我呢,也是到现在公司后才开始接触并使用,对其“开发”的还不够深,这里... 查看详情

浅谈ioc

一、引言IOC-InvertionofControl,即控制反转,是一种程序设计思想,世上本没有路,走的人多了便有了路,本文将一步步带你了解IOC设计思想的演进之路。在学习IOC之前我们先初步了解几个概念依赖(Dependency):就是有联系,表示一... 查看详情

浅谈系统架构(代码片段)

...工作。为什么要做架构**架构设计的主要目的是为了解决软件系统复杂度带来的问题**那么系统的复杂度主要来源于哪些方面呢?其主要来源与以下几个方面1)高性能2) 查看详情

您如何设计基于 Erlang/OTP 的分布式容错多核系统的架构?

】您如何设计基于Erlang/OTP的分布式容错多核系统的架构?【英文标题】:HowdoyoudesignthearchitectureofanErlang/OTP-baseddistributedfault-tolerantmulticoresystem?【发布时间】:2011-11-1013:55:41【问题描述】:我想构建一个基于Erlang/OTP的系统来解决... 查看详情

性能基础之浅谈常见接口性能压测

...edArchitecture,面向服务架构)是目前通用的组件模型。它将软件系统的不同功能模块(被称为服务)通过接口的形式联系起来。这里的接口可以是具体的接口服务也可以是连接两个模块通信的中间件。一个大型项目通常是由多个系... 查看详情

浅谈秒杀系统架构设计

http://mp.weixin.qq.com/s?__biz=MjM5NDM4MDIwNw%3D%3D&mid=2448834705&idx=1&sn=25cf3d4f6d6826e564a634901189eb8f&chksm=b28a405185fdc9478b6bd140396a37bbc2af68bdbd1fdf3ec9377e07ef5ce457849d 查看详情