Spoiled for choice: Dealing with dependencies in software development

Make or buy? Decisions to use frameworks or third-party software often lead to discussions about scope, quality and thus the sensible use of time and money. We show why it does not always make sense to rely on ready-made solutions and when in-house development is worthwhile.

Make-or-buy? Decisions to use frameworks or third-party software often lead to discussions about scope, quality and thus the sensible use of time and money. We show why it does not always make sense to rely on ready-made solutions and when in-house development is worthwhile.

So how do you weigh it?

The decision to develop a software in-house or to use a ready-made software is usually based on little experience. In addition, one may only know at a late stage whether there are functional differences that are not apparent at first glance. They only become apparent later in the implementation - a problem that cannot be completely eliminated.

So if you want to make a good decision, you need factors from which you can deduce with little knowledge whether make or buy is the better choice. The following tricks can simplify the decision.

Solution scope

That offers so much, we are certainly well positioned for the future.

This or something similar could be the argumentation if only a small part of the third-party software is needed. But what if only the small part is needed? Isn't it then easier to write the functionality yourself? Do I really need a component like leftpad?

It is useful to take a look at the scope of the needed solution. Sometimes real arguments for the more complex off-the-shelf solution don't exist at all and you still decide for a potential you won't use at all.

Future-proof

External components are independently maintained and updated

It is certainly true that components from other manufacturers often have updates and security updates that increase functionality and improve stability. It is the acquired maturity of a third-party component that provides insight into its future-proofing. Factors include:

  • Commitment, popularity (for open source software).
  • Software has a stable business model, with which the manufacturer earns money
  • Enforcement in the market

These factors seem easy to evaluate, for example, the stars on GitHub give an indication of the popularity of the software. The assertion in the market or the question of the business model often remains only qualitatively evaluable.

The integration effort remains

Regardless of the previous factors, a certain integration effort always remains. In the case of in-house development, this is included. With third-party software, integration often consists of building understanding through documentation and trial and error. This structure tends to be larger with more extensive solutions. With smaller solutions, it can therefore make sense to develop them future-proof yourself, because with in-house development, your own development team knows the solution very well and can keep it stable.

Nothing is free

Neither is free of charge, because up-to-dateness always costs integration effort in any case, even if automated version management in the package managers and approaches such as semantic versioning are used to try to escape dependency hell (e.g. through many dependencies and dependency cascades).

A practical example - Caliburn.Micro

How easy an implementation can be can be seen in an example from one of our software teams who create software using WPF and .NET. For this they used the Caliburn.Micro framework. The primary reason was to use the MVVM pattern.

The decision was easy because Caliburn.Micro is free and very easy to use and integrate. By limiting themselves to the MVVM pattern, the team had chosen only a small isolated part that was important to them. Nevertheless, problems arose relatively early on that made development unnecessarily complicated:

  • In the MVVM pattern, as implemented in Caliburn.Micro, the matching of view and view model happens via the name of the components.
  • With the additional integration of DevExpress, this pattern could not be used in this way. Instead, the standard WPF pattern had to be used, consisting of XAML + code-behind.
  • By using two patterns, the team kept falling into the WPF pattern because it was not clear at which point which pattern was now being used.

So first, the scope of the solution was considered. The MVVM pattern itself is a smaller isolated part. It can be easily separated out and solved by itself, without dependency on Caliburn.Micro. Integration and familiarization with the external framework was similarly complex.

Accordingly, the implementation of the in-house development included:

  • Isolating the MVVM pattern matching via filename convention (instead of component names) and
  • the integration into WPF

Own ideas can now be implemented easier and faster and the code is much clearer and leaner, because only those functions were implemented that we actually need. Our framework is now much easier to understand and therefore ready for any maintenance and extension. The use of our proprietary standard formats and standard structures simplify the handling of the software within the company. Thus, problems can be solved more easily by already existing solutions.

The fact that it does not require a lot of programming work is shown by the code we created ourselves, which reflects the advantages hoped for through Caliburn.Micro.

 

///

/// Searches for View models and views that match the name convention and adds them to the resource dictionary ///

/// The namespace in which will be searched for the view models ///Reference to Dictionary in which the template should be stored ///Array of types that contains among other types, the view model types and view types /// List that contains the imported templates public static List GenerateTemplateDictionary(String typeNamespace, ResourceDictionary res, Type[] relTypes) { // get names of namespaces for VMs and Views by convention String vmNs = $@"{typeNamespace}.ViewModels"; String viewNs = vmNs.Replace("Model", String.Empty); // getting types of VMs and Views from that namespaces var viewModelQuery = from t in relTypes where t != null && t.IsClass && !t.IsAbstract && t.Namespace != null && t.Namespace.StartsWith(vmNs) select t; var viewModelTypeList = viewModelQuery.Where(t => t != null).GroupBy(t => t.Name).Select(g => g.First()).ToDictionary(t => t.Name, t => t); var viewQuery = from t in relTypes where t != null && t.IsClass && !t.IsAbstract && t.Namespace != null && t.Namespace.StartsWith(viewNs) select t; var viewTypeList = viewQuery.Where(t => t != null).GroupBy(t => t.Name).Select(g => g.First()).ToDictionary(t => t.Name, t => t); var foundItems = new List(); // link the VM types to View types by convention into a datatemplate and add the datatemplate to app.resources foreach (var vmt in viewModelTypeList) { var viewKey = vmt.Key.Replace("Model", String.Empty); if (viewTypeList.ContainsKey(viewKey)) { var viewType = viewTypeList[viewKey]; var template = CreateTemplate(vmt.Value, viewType); if (!res.Contains(template.DataTemplateKey ?? throw new InvalidOperationException())) { res.Add(template.DataTemplateKey ?? throw new InvalidOperationException(), template); foundItems.Add($@"Found and add VM -> View: {viewType.FullName} -> {vmt.Value.FullName}"); } else foundItems.Add($@"Pair is already known VM -> View: {viewType.FullName} -> {vmt.Value.FullName}"); } } return foundItems; } private static DataTemplate CreateTemplate(Type viewModelType, Type viewType) { // The only way to get around some problems with binding later is this way of creating datatemplate objects // see www.ikriv.com/dev/wpf/DataTemplateCreation/ const String xamlTemplate = ""; var xaml = String.Format(xamlTemplate, viewModelType.Name, viewType.Name); var context = new ParserContext { XamlTypeMapper = new XamlTypeMapper(new String[0]) }; context.XamlTypeMapper.AddMappingProcessingInstruction("vm", viewModelType.Namespace ?? throw new InvalidOperationException(), viewModelType.Assembly.FullName); context.XamlTypeMapper.AddMappingProcessingInstruction("v", viewType.Namespace ?? throw new InvalidOperationException(), viewType.Assembly.FullName); caliburnmicro.com context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"); context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml"); context.XmlnsDictionary.Add("vm", "vm"); context.XmlnsDictionary.Add("v", "v"); var template = (DataTemplate)XamlReader.Parse(xaml, context); return template; }

 

 

The use is very simple.

DynamicVmTemplateFinder.GenerateTemplateDictionary(typeof(App), Current.Resources,  Assembly.GetExecutingAssembly().GetTypes())

The dictionary takes care of the matching by binding the view and view model together.

That the decision was right became clear in June 2020 at the latest. At that time Caliburn.Micro was marked as no longer maintained by the developer.

[Translate to English:] Quelle: https://caliburnmicro.com/

The reasons are often the same. There are few developers responsible for the project, the developers change their private or professional setup and can no longer maintain a development that is often done privately.

Conclusion

The example shows that decisions and considerations in this case meant that the investment was worthwhile. The freedom to make this decision in the development team characterizes the work at OHB Digital Services.

There is plenty of scope for making these decisions, people can contribute their own ideas and the project manager does not dictate which type of library is to be used. The fact that decisions are made as a team often results in better solutions.

For us, it has become clear over time that it is more often sensible to create our own solutions and develop a simple small framework ourselves, rather than using more complex third-party frameworks.

If you still decide to use a third-party library, you should always be aware that this is not a no-brainer. Instead of simply integrating and forgetting, a high level of care and maintenance is also important with third-party software. If the number of dependencies increases, one sometimes loses sight of hidden incompatibilities. Costly bug fixes can also be the result if the third-party software is not deeply understood. All this must not be forgotten.

In the end, of course, the best possible product should be available for the customer and the decision should be made according to this.

Your journey with OHB Digital Services

Does this sound interesting for you and your company?
Then get in touch with us.