-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Restructure windows service template #4
Conversation
The build is failing because I haven't updated the approval tests, but I'd like this to be reviewed before proceeding any further. |
@@ -5,6 +5,7 @@ | |||
<Description>dotnet new templates targeting Particular tools and libraries</Description> | |||
<IncludeBuildOutput>false</IncludeBuildOutput> | |||
<PackageType>Template</PackageType> | |||
<LangVersion>latest</LangVersion> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why add this? There's no code in this project, though I guess it doesn't hurt to have it. We could add this and TreatWarningsAsErrors to Directory.Build.props instead. We'd have to consider that it would propagate to the template projects by default though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is code in this project due to:
<ItemGroup>
<None Include="..\Templates\**\*.*" Exclude="**\bin\**\*.*;**\obj\**\*.*" Pack="true" PackagePath="content" />
</ItemGroup>
Indeed, when you open the solution, this is the project that is opened.
I added it because I wanted to use async main
etc. but I'll address that consideration in response to your other comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no compiled code. Note that the include is for the None
group.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed that. 😄
OK, in that case I'll remove this element, since it's serving no purpose.
@@ -4,6 +4,7 @@ | |||
<OutputType>Exe</OutputType> | |||
<TargetFramework>net462</TargetFramework> | |||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | |||
<LangVersion>latest</LangVersion> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be setting this in our template project? I'm wondering if we should even be setting TreatWarningsAsErrors
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, I added this because I wanted to use async main
.
Console.CancelKeyPress += (sender, e) => { tcs.SetResult(null); }; | ||
|
||
await host.Start(); | ||
await Console.Out.WriteLineAsync("Press Ctrl+C to exit..."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why Console.Out
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to be explicit about whether I am writing to stdout or stderr. Also, Console.Out
gives us WriteLineAsync()
, where as Console
only gives us WriteLine()
. I guess it doesn't make much difference, but given that we are already in an async context, we may as well use the async method.
However, I don't have a strong preference here. Happy to switch to Console.WriteLine()
if that is preferred.
|
||
namespace NServiceBusWindowsService | ||
{ | ||
internal static class Program |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of the internal
access modifiers seem unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to always be explicit. I don't like reading code and having to mentally map back to the specific defaults for each type of member. E.g. for classes it's internal
, for fields and method it's private
, etc.
Incidentally, this is also the preference of the dotnet org:
- We always specify the visibility, even if it's the default (i.e.
private string _foo
notstring _foo
). Visibility should be the first modifier (i.e.
public abstract
notabstract public
).
However, if @Particular/host-maintainers prefer not to restate the defaults then I'll remove them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My vote is to remove them, primarily because none of the default Microsoft templates include them, and I'd like to see our stuff match the default templates as much as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, that's a reasonable argument. I'll remove them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we come up with a company wide approach to this? I'm too old to remember settings like this on a repo basis. And no I won't install R# 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm generally 👎 to company wide standards, but I do think the code should tell us what the standard for a repo is, without imposing a local tooling choice (e.g. R#). It could be done via EditorConfig (now built into VS), Roslyn analyzers, etc.
{ | ||
internal static class Program | ||
{ | ||
public async static Task Main(string[] args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While using async Main is nice, do we want our templates to have a C# 7.1 dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good point, and I'm glad you brought it up. I committed this with some doubt and I was hoping that someone would call it out. 😄
Perhaps C# 7.1 is too much for now. What would we find acceptable? 7.0? 6.0? Interestingly, while we should ensure that the templates comply with the minimum version we want to support, the content of the langVersion
tag in the templates is another matter. E.g. it could still say latest
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that we are using SDK-style csproj, the oldest version of VS supported is 2017, and that comes with C# 7.0, so I propose that we change language version to C# 7.0.
.editorconfig
Outdated
@@ -0,0 +1,7 @@ | |||
root = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do all of these editorconfig values do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They set the preference to not use this.
. It's only a partial representation of the preferences for this repo, but it's a start. The reason I added these specific settings is to override my global machine settings which prefer this.
I can remove it from this PR if preferred. In fact, I will, since it's an unrelated change.
@bording I amended the commit to remove the addition of |
I amended the commit to remove the addition of |
I pushed a fixup to change the lang version to C# 7 and updated the approved files. This is still a WIP because the |
I pushed another fixup to fix line endings 🙄 |
@seanfarmar and I worked on this this morning and we extracted the common endpoint settings into a file which should ultimately be shared with all endpoints. |
@@ -4,6 +4,8 @@ | |||
<OutputType>Exe</OutputType> | |||
<TargetFramework>net462</TargetFramework> | |||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | |||
<!-- TODO: consider using a later version of C# --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, not sure how I feel about this, vs just using the default behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that the template is using SDK-style csproj, which requires VS 2017, and C# 7 is lowest possible default in VS 2017, this is redundant, so I'll remove it.
namespace NServiceBusWindowsService | ||
{ | ||
// TODO: move this into a shared project for use in all endpoints | ||
internal static class EndpointConfigurationExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of feel like this is going a bit too far with the template.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, IMO we should remove it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My personal preference is to keep it. It leads the user down the right path for when they add the next project to the solution, and it makes the Host.Start()
code read very nicely.
// TODO: choose a durable transport for production | ||
// https://docs.particular.net/transports/ | ||
var transportExtensions = endpointConfiguration.UseTransport<LearningTransport>(); | ||
applyEndpointSpecificSettings?.Invoke(transportExtensions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this being invoked here specifically? Why not after everything else in here has been called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, and I have no good answer. I'll move it to the end.
One other thing to consider, though perhaps in another PR, The So we could extend this template to work with netcoreapp2.0 as well. Maybe it would be better to wait until the package is not prerelease? |
|
||
namespace NServiceBusWindowsService | ||
{ | ||
internal class Host |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to keep all the redundant internal
and private
modifiers around?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to always have them, but @bording makes a good case for removing them in the template, so I'm going to remove them.
|
||
private async Task OnCriticalError(ICriticalErrorContext context) | ||
{ | ||
// TODO: decide if stopping the endpoint and exiting the process is the best response to a critical error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps add a link to https://docs.particular.net/nservicebus/hosting/windows-service?version=core_7#installation-restart-recovery to remind the users that they should setup Service Recovery?
namespace NServiceBusWindowsService | ||
{ | ||
// TODO: move this into a shared project for use in all endpoints | ||
internal static class EndpointConfigurationExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, IMO we should remove it
private IEndpointInstance endpoint; | ||
|
||
// TODO: give the endpoint an appropriate name | ||
public string EndpointName => "MyNServiceBusWindowsService"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be a get only property instead of a public field? e.g.:
public string EndpointName{ get; } => "MyNServiceBusWindowsService";
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public string Foo => "Bar";
is already a get-only property. That is why there is an expression rather than an assignment. public string Foo { get; } => "Bar";
is invalid syntax. You can have public string Foo { get; } = "Bar";
but that is an initial assignment, rather than an expression body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I was writing from the phone and I just copy / pasted adding the {get;}
.
public string Foo => "Bar";
is already a get-only property.
Now I have one more reason to dislike expression bodied members 😄
} | ||
} | ||
|
||
private async Task ExitProcess(string message, Exception exception) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to rename this to something more meaningful, as a caller I do not expect ExitProcess
to log a fatal and then fail fast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andreasohlund @bording @mauroservienti I squashed the pre-review commits (the fixups were getting complicated) and I pushed a new fixup which hopefully takes care of the review comments.
Please see my inline comments and advise.
There is also the remaining question of whether we want the separate EndpointConfigurationExtensions
class or not. Personally I prefer it, since it guides the user to the pit of success for when the next project is added to the solution, and it also makes for good reading in the Host
:
endpointConfiguration.ApplySettingsForAllEndpoints(transport =>
{
// TODO: apply endpoint-specific transport settings
});
Perhaps we just need to vote on whether to keep it? Currently myself and @seanfarmar are 👍, @mauroservienti is 👎, and @bording sounds undecided but leaning towards 👎 (@bording can you please confirm your opinion?).
} | ||
} | ||
|
||
async Task LogFatalAndFailFast(string message, Exception exception) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name is a bit verbose. I'll gladly change it if we can think of something better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just FailFast
? Does the fact that it also logs need to be in the name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a reaction to @mauroservienti's comment:
I'd prefer to rename this to something more meaningful, as a caller I do not expect
ExitProcess
to log a fatal and then fail fast.
I made it completely explicit to remove any ambiguity. However, I'm happy with FailFast
and in fact I did think about calling it that in my last commit. I'll make that change.
await Task.CompletedTask; | ||
|
||
// TODO: https://docs.particular.net/nservicebus/hosting/windows-service#installation-restart-recovery | ||
Environment.FailFast(message, exception); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I previously had this in a finally
, with the rest of the method in a try
, but because this is a FailFast()
, it would mask any exceptions thrown in the try
, effectively causing them to be swallowed. However, that means that if there is an exception in the log flushing, that error will bubble out of the process instead of the exception that caused the problem in the endpoint, and if we have to choose between the two, we probably want the exception from the endpoint rather than the exception from the log flushing, so perhaps I should re-instate the try...finally
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exceptions would be swallowed? What else would be in this method that you're concerned about? If the logger throws when we're shutting down anyway, that seems fine to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is, that if the logging framework throws then that exception will be thrown out to the runtime as the reason that the process stopped, and the exception that actually caused the endpoint to shutdown in the first place, while still being a first chance exception and therefore perhaps being logged somewhere, is effectively being swallowed. I think it may be better for me to re-instate the try..finally
, so that the log fatal/log flushing becomes "best effort" and we ensure that the exception being passed to the method is the one that the runtime sees as causing the process to stop via the fail-fast.
@adamralph I've taken another look at the I think we'd be better off moving all of that back into the single place, and we can have another comment that says something about considering sharing the configuration if they have more than one endpoint. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this approach, I would merge this and maybe do another round or refactoring later
@bording that's a valid point and, on reflection, I don't like it either. I'm changing my opinion on the |
I've pushed another fixup. Note that |
Given that very few users is expected to use the ScAdapter I'd say we treat this as a separate PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume we don't use .ConfigureAwait(false)
since we know this is a windows service
@mauroservienti seems like your comments have been addressed? |
Do we want to address
yup, approved. |
// https://docs.particular.net/nservicebus/serialization/ | ||
endpointConfiguration.UseSerialization<NewtonsoftSerializer>(); | ||
|
||
endpointConfiguration.DefineCriticalErrorAction(OnCriticalError); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line seems to have disappeared from the new version of template, so OnCriticalError
will never be called now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch @bording !
|
||
// TODO: when using an external logging framework it is important to flush any pending entries prior to calling FailFast | ||
// https://docs.particular.net/nservicebus/hosting/critical-errors#when-to-override-the-default-critical-error-action | ||
await Task.CompletedTask; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we awaiting a Task.CompletedTask
here? Why is the method async? Why not just return void here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea lets make this void.
One argument could be to prep for loggers becoming async but aspnet/Logging#444 is a good description on why that's not that likely
@mauroservienti Since this PR is from @adamralph's fork, If he didn't give us rights to push back to his branch, we might need to open a new PR to deal with additional changes. Alternatively, we go ahead and merge this as-is, and then fix the remaining items in a new PR. |
Adam is off this week so lets merge and fix in a separate PR? |
ad1109e
Looks like I was able to push changes to his fork, so I went ahead and fixed up the final items. |
One additional minor tweak: Since the template engine replaces all instances of "NServiceBusWindowsService", the endpoint name also gets changed, so there's no need for the "My" in the name. |
@seanfarmar @bording this is one opinion on how the templates could look. I've only changed the
NServiceBusWindowsService
template so far.