Skip to content

Conversation

wbtoms
Copy link

@wbtoms wbtoms commented Feb 28, 2025

Proposed changes

When integrating UA-.NETStandard, it is difficult to perform unit tests or even integration while you basically need to setup almost all objects from the application instance to the node managers with their actual implementations. No possibility to mock them or to create what is just necessary.

It is also diffiicult to replace an implementation of a given component by another custom implementation.

Therefore I propose this change which is, a first step, to introduce dependency injections for:

  • Application instance
  • Server
  • Node managers

Related Issues

No related open issue found.

Types of changes

What types of changes does your code introduce?
Put an x in the boxes that apply. You can also fill these out after creating the PR.

  • Bugfix (non-breaking change which fixes an issue)
  • Enhancement (non-breaking change which adds functionality)
  • Test enhancement (non-breaking change to increase test coverage)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected, requires version increase of Nuget packages)
  • Documentation Update (if none of the other choices apply)

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • I have read the CONTRIBUTING doc.
  • I have signed the CLA.
  • I ran tests locally with my changes, all passed.
  • I fixed all failing tests in the CI pipelines.
  • I fixed all introduced issues with CodeQL and LGTM.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added necessary documentation (if appropriate).
  • Any dependent changes have been merged and published in downstream modules.

Further comments

If this proposition is interresting and at some point is merged, it might be interresting also to continue with orther types of object especially everything around NodeState. And of course, the client library should be treated the same way. But before going further, let see if this make sense for the audience.

@CLAassistant
Copy link

CLAassistant commented Feb 28, 2025

CLA assistant check
All committers have signed the CLA.

@wbtoms wbtoms force-pushed the IntroduceDependencyInjection branch from 6feb171 to 357cb10 Compare February 28, 2025 08:36
@wbtoms wbtoms force-pushed the IntroduceDependencyInjection branch from 357cb10 to c326401 Compare February 28, 2025 09:57
Copy link

codecov bot commented Feb 28, 2025

Codecov Report

Attention: Patch coverage is 64.73988% with 61 lines in your changes missing coverage. Please review.

Project coverage is 56.65%. Comparing base (6bbe07c) to head (c4b74f7).

Files with missing lines Patch % Lines
...aries/Opc.Ua.Server/NodeManager/CoreNodeManager.cs 19.35% 24 Missing and 1 partial ⚠️
...braries/Opc.Ua.Server/Server/ServerInternalData.cs 73.68% 16 Missing and 4 partials ⚠️
Libraries/Opc.Ua.Server/Server/StandardServer.cs 65.38% 2 Missing and 7 partials ⚠️
...raries/Opc.Ua.Configuration/ApplicationInstance.cs 28.57% 5 Missing ⚠️
...ies/Opc.Ua.Server/NodeManager/MasterNodeManager.cs 89.47% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3022      +/-   ##
==========================================
+ Coverage   56.64%   56.65%   +0.01%     
==========================================
  Files         356      359       +3     
  Lines       68435    68477      +42     
  Branches    14072    14080       +8     
==========================================
+ Hits        38765    38797      +32     
- Misses      25491    25493       +2     
- Partials     4179     4187       +8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@romanett
Copy link
Contributor

romanett commented Mar 1, 2025

/azp run

Copy link

Pull request contains merge conflicts.

@wbtoms wbtoms force-pushed the IntroduceDependencyInjection branch from c326401 to 7dfa024 Compare March 7, 2025 10:05
@wbtoms wbtoms requested a review from romanett March 7, 2025 10:06
@wbtoms wbtoms marked this pull request as ready for review March 9, 2025 11:26
@romanett
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

ConfigSectionName = configSectionName,
CertificatePasswordProvider = PasswordProvider
};
var passwordProvider = new CertificatePasswordProvider(Password);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

previously LoadAsync created a new application instance, now it doesnt.

I need to check...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, now this object comes from the DI. This was the intension.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah but if you call load twice previously a new instance was created now the Same is reused. I need to Check If this could cause unwanted behaviour changes

Copy link
Author

@wbtoms wbtoms Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, for this shadow config use case, right? I think this works becauce LoadApplicationConfiguration sets the application configuration to null before relaoding it so it is always a complete new instance of the configuration.

But what is the shadow configuration use case for excatly? do you know?

@wbtoms wbtoms force-pushed the IntroduceDependencyInjection branch from 6cce6c8 to c4b74f7 Compare March 13, 2025 16:42
Copy link
Contributor

@romanett romanett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this valuable additions. We really appreciate it.

Please add the new constructors as an option in addition to the existing ones. We cannot break all existing implementations with this PR.

@romanett romanett added this to the 1.5.376 Maintenance Update milestone Apr 21, 2025
@romanett romanett removed this from the 1.5.376 Maintenance Update milestone May 6, 2025
@marcschier marcschier marked this pull request as draft June 26, 2025 13:37
@marcschier marcschier marked this pull request as draft June 26, 2025 13:37
@romanett
Copy link
Contributor

@wbtoms I will gradually introduce these changes with multiple PRs in a more compatible manner. Thank you for all the work you put into this.

@mregen mregen requested a review from Copilot July 31, 2025 05:18
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces dependency injection for core server components to improve testability and enable easier integration of the UA-.NETStandard library. The changes add interfaces for key server classes and provide DI service registration methods.

  • Introduces interfaces for major server components (IStandardServer, IServerInternal, IApplicationInstance, etc.)
  • Adds service collection extension methods for registering server services
  • Updates tests to use dependency injection for server instantiation

Reviewed Changes

Copilot reviewed 49 out of 50 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Multiple test files Updated to use DI container for server instantiation
IStandardServer.cs New interface for StandardServer
IApplicationInstance.cs New interface for ApplicationInstance
IServerInternal.cs Enhanced interface with additional methods
ServiceCollectionExtensions.cs DI registration methods
StandardServer.cs Implements IStandardServer, requires DI parameters
Various other interface files New interfaces for core components

NodeId[] testSet = CommonTestWorkers.NodeIdTestSetStatic.Select(n => ExpandedNodeId.ToNodeId(n, namespaceUris)).ToArray();
transferRequestHeader.Timestamp = DateTime.UtcNow;
var subscriptionIds = CommonTestWorkers.CreateSubscriptionForTransfer(serverTestServices, transferRequestHeader, testSet, kQueueSize, -1);
var subscriptionIds = CommonTestWorkers.CreateSubscriptionForTransfer(serverTestServices, transferRequestHeader, testSet, kQueueSize);
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method call is missing a parameter. The original call had 5 parameters including a -1 value, but now only 4 parameters are provided. This suggests the method signature changed but this call wasn't fully updated.

Suggested change
var subscriptionIds = CommonTestWorkers.CreateSubscriptionForTransfer(serverTestServices, transferRequestHeader, testSet, kQueueSize);
var subscriptionIds = CommonTestWorkers.CreateSubscriptionForTransfer(serverTestServices, transferRequestHeader, testSet, kQueueSize, -1);

Copilot uses AI. Check for mistakes.

m_nodeManagerFactories = new List<INodeManagerFactory>();
m_applicationInstance = applicationInstance ?? throw new ArgumentNullException(nameof(applicationInstance));
m_serverInternal = serverInternal ?? throw new ArgumentNullException(nameof(serverInternal));
m_mainNodeManagerFactory = mainNodeManagerFactory ?? throw new ArgumentNullException(nameof(mainNodeManagerFactory)); ;
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra semicolon at the end of this line. The double semicolon should be reduced to a single semicolon.

Suggested change
m_mainNodeManagerFactory = mainNodeManagerFactory ?? throw new ArgumentNullException(nameof(mainNodeManagerFactory)); ;
m_mainNodeManagerFactory = mainNodeManagerFactory ?? throw new ArgumentNullException(nameof(mainNodeManagerFactory));

Copilot uses AI. Check for mistakes.

Comment on lines +279 to +292
public void CreateServerObject(EventManager eventManager, ResourceManager resourceManager, RequestManager requestManager);

/// <summary>
/// Stores the MasterNodeManager and the CoreNodeManager
/// </summary>
/// <param name="nodeManager">The node manager.</param>
public void SetNodeManager(IMasterNodeManager nodeManager);

/// <summary>
/// Stores the SessionManager, the SubscriptionManager in the datastore.
/// </summary>
/// <param name="sessionManager">The session manager.</param>
/// <param name="subscriptionManager">The subscription manager.</param>
public void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface method should not use the 'public' access modifier within an interface definition. Interface methods are implicitly public.

Suggested change
public void CreateServerObject(EventManager eventManager, ResourceManager resourceManager, RequestManager requestManager);
/// <summary>
/// Stores the MasterNodeManager and the CoreNodeManager
/// </summary>
/// <param name="nodeManager">The node manager.</param>
public void SetNodeManager(IMasterNodeManager nodeManager);
/// <summary>
/// Stores the SessionManager, the SubscriptionManager in the datastore.
/// </summary>
/// <param name="sessionManager">The session manager.</param>
/// <param name="subscriptionManager">The subscription manager.</param>
public void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);
void CreateServerObject(EventManager eventManager, ResourceManager resourceManager, RequestManager requestManager);
/// <summary>
/// Stores the MasterNodeManager and the CoreNodeManager
/// </summary>
/// <param name="nodeManager">The node manager.</param>
void SetNodeManager(IMasterNodeManager nodeManager);
/// <summary>
/// Stores the SessionManager, the SubscriptionManager in the datastore.
/// </summary>
/// <param name="sessionManager">The session manager.</param>
/// <param name="subscriptionManager">The subscription manager.</param>
void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);

Copilot uses AI. Check for mistakes.

Comment on lines +279 to +292
public void CreateServerObject(EventManager eventManager, ResourceManager resourceManager, RequestManager requestManager);

/// <summary>
/// Stores the MasterNodeManager and the CoreNodeManager
/// </summary>
/// <param name="nodeManager">The node manager.</param>
public void SetNodeManager(IMasterNodeManager nodeManager);

/// <summary>
/// Stores the SessionManager, the SubscriptionManager in the datastore.
/// </summary>
/// <param name="sessionManager">The session manager.</param>
/// <param name="subscriptionManager">The subscription manager.</param>
public void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface method should not use the 'public' access modifier within an interface definition. Interface methods are implicitly public.

Suggested change
public void CreateServerObject(EventManager eventManager, ResourceManager resourceManager, RequestManager requestManager);
/// <summary>
/// Stores the MasterNodeManager and the CoreNodeManager
/// </summary>
/// <param name="nodeManager">The node manager.</param>
public void SetNodeManager(IMasterNodeManager nodeManager);
/// <summary>
/// Stores the SessionManager, the SubscriptionManager in the datastore.
/// </summary>
/// <param name="sessionManager">The session manager.</param>
/// <param name="subscriptionManager">The subscription manager.</param>
public void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);
void CreateServerObject(EventManager eventManager, ResourceManager resourceManager, RequestManager requestManager);
/// <summary>
/// Stores the MasterNodeManager and the CoreNodeManager
/// </summary>
/// <param name="nodeManager">The node manager.</param>
void SetNodeManager(IMasterNodeManager nodeManager);
/// <summary>
/// Stores the SessionManager, the SubscriptionManager in the datastore.
/// </summary>
/// <param name="sessionManager">The session manager.</param>
/// <param name="subscriptionManager">The subscription manager.</param>
void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);

Copilot uses AI. Check for mistakes.

/// </summary>
/// <param name="sessionManager">The session manager.</param>
/// <param name="subscriptionManager">The subscription manager.</param>
public void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This interface method should not use the 'public' access modifier within an interface definition. Interface methods are implicitly public.

Suggested change
public void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);
void SetSessionManager(SessionManager sessionManager, SubscriptionManager subscriptionManager);

Copilot uses AI. Check for mistakes.

/// <summary>
/// Stores the MonitoredItemQueueFactory in the datastore.
/// </summary>
/// <param name="monitoredItemQueueFactory">The MonitoredItemQueueFactory.</param>
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] This method is missing documentation while other methods in the interface have XML documentation comments. For consistency, all public interface methods should be documented.

Suggested change
/// <param name="monitoredItemQueueFactory">The MonitoredItemQueueFactory.</param>
/// <param name="monitoredItemQueueFactory">The MonitoredItemQueueFactory.</param>
/// <summary>
/// Stores the MonitoredItemQueueFactory in the datastore.
/// </summary>
/// <param name="monitoredItemQueueFactory">The factory used to create monitored item queues.</param>

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants