Skip to content
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

Merged
merged 8 commits into from
Nov 28, 2017
Merged

Restructure windows service template #4

merged 8 commits into from
Nov 28, 2017

Conversation

adamralph
Copy link
Member

@seanfarmar @bording this is one opinion on how the templates could look. I've only changed the NServiceBusWindowsService template so far.

@adamralph
Copy link
Member Author

The build is failing because I haven't updated the approval tests, but I'd like this to be reviewed before proceeding any further.

@adamralph adamralph changed the title restructure [WIP] restructure Nov 13, 2017
@@ -5,6 +5,7 @@
<Description>dotnet new templates targeting Particular tools and libraries</Description>
<IncludeBuildOutput>false</IncludeBuildOutput>
<PackageType>Template</PackageType>
<LangVersion>latest</LangVersion>
Copy link
Member

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.

Copy link
Member Author

@adamralph adamralph Nov 14, 2017

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.

Copy link
Member

@bording bording Nov 14, 2017

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.

Copy link
Member Author

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>
Copy link
Member

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.

Copy link
Member Author

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...");
Copy link
Member

Choose a reason for hiding this comment

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

Why Console.Out?

Copy link
Member Author

@adamralph adamralph Nov 14, 2017

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
Copy link
Member

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.

Copy link
Member Author

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:

  1. We always specify the visibility, even if it's the default (i.e.
    private string _foo not string _foo). Visibility should be the first modifier (i.e.
    public abstract not abstract public).

However, if @Particular/host-maintainers prefer not to restate the defaults then I'll remove them.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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# 😃

Copy link
Member Author

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)
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member Author

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
Copy link
Member

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?

Copy link
Member Author

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.

@adamralph
Copy link
Member Author

@bording I amended the commit to remove the addition of .editorconfig.

@adamralph
Copy link
Member Author

I amended the commit to remove the addition of <LangVersion>latest</LangVersion> to ParticularTemplates.csproj.

@adamralph
Copy link
Member Author

I pushed a fixup to change the lang version to C# 7 and updated the approved files. This is still a WIP because the ScAdapterService is yet to be changed.

@adamralph
Copy link
Member Author

I pushed another fixup to fix line endings 🙄

@adamralph
Copy link
Member Author

adamralph commented Nov 15, 2017

@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# -->
Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member

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

Copy link
Member Author

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);
Copy link
Member

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?

Copy link
Member Author

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.

@bording
Copy link
Member

bording commented Nov 15, 2017

One other thing to consider, though perhaps in another PR, The ServiceBase class is now available for .NET Core when running on windows: https://www.nuget.org/packages/System.ServiceProcess.ServiceController/4.5.0-preview1-25914-04

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
Copy link
Member

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?

Copy link
Member Author

@adamralph adamralph Nov 22, 2017

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
Copy link
Member

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
Copy link
Member

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";
Copy link
Member

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";

Copy link
Member Author

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.

Copy link
Member

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)
Copy link
Member

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.

Copy link
Member Author

@adamralph adamralph left a 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)
Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member Author

@adamralph adamralph Nov 24, 2017

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);
Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member Author

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.

@bording
Copy link
Member

bording commented Nov 23, 2017

@adamralph I've taken another look at the EndpointConfigurationExtensions class, and I'm 👎 for keeping it. The fact that the second parameter has the learning transport baked in is worrying, since that's one more place users would have to modify.

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.

seanfarmar
seanfarmar previously approved these changes Nov 24, 2017
Copy link
Contributor

@seanfarmar seanfarmar left a 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

@adamralph
Copy link
Member Author

The fact that the second parameter has the learning transport baked is worrying, since that's one more place users would have to modify.

@bording that's a valid point and, on reflection, I don't like it either. I'm changing my opinion on the EndpointConfigurationExtensions and given that we now have a majority 👎 vote, I'm going to remove it.

@adamralph
Copy link
Member Author

I've pushed another fixup. Note that ScAdapterService is still unchanged in this PR so it also needs work to close this out.

@andreasohlund andreasohlund changed the title [WIP] restructure Restructure windows service template Nov 27, 2017
@andreasohlund
Copy link
Member

I've pushed another fixup. Note that ScAdapterService is still unchanged in this PR so it also needs work to close this out.

Given that very few users is expected to use the ScAdapter I'd say we treat this as a separate PR

andreasohlund
andreasohlund previously approved these changes Nov 27, 2017
Copy link
Member

@andreasohlund andreasohlund left a 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

https://stackoverflow.com/a/23515178/236004

@andreasohlund
Copy link
Member

@mauroservienti seems like your comments have been addressed?

mauroservienti
mauroservienti previously approved these changes Nov 27, 2017
@mauroservienti
Copy link
Member

I've pushed another fixup. Note that ScAdapterService is still unchanged in this PR so it also needs work to close this out.

Do we want to address ScAdapterService in this PR? I'm 👎, we should raise another PR.

seems like your comments have been addressed?

yup, approved.

// https://docs.particular.net/nservicebus/serialization/
endpointConfiguration.UseSerialization<NewtonsoftSerializer>();

endpointConfiguration.DefineCriticalErrorAction(OnCriticalError);
Copy link
Member

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.

Copy link
Member

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;
Copy link
Member

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?

Copy link
Member

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

@bording
Copy link
Member

bording commented Nov 27, 2017

@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.

@andreasohlund
Copy link
Member

Adam is off this week so lets merge and fix in a separate PR?

@bording
Copy link
Member

bording commented Nov 28, 2017

Looks like I was able to push changes to his fork, so I went ahead and fixed up the final items.

bording
bording previously approved these changes Nov 28, 2017
@bording
Copy link
Member

bording commented Nov 28, 2017

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.

@andreasohlund andreasohlund merged commit c7347b7 into Particular:develop Nov 28, 2017
@adamralph adamralph deleted the restructure branch December 5, 2017 07:33
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.

None yet

5 participants