DotNetFringe 2015 Recap From a Former .NET Developer


    DotNetFringe 2015 Recap From a Former .NET Developer

    As some may or may not know, I have quite the storied past in .NET. The year was 2008 and I just landed my first job as an internal IT developer for a largeish corporation in San Antonio, TX. The first project I was on was a WinForms application written in C#, .NET 2.X if I recall correctly. It was built using CAB, CAB was a rather complicated and possibly controversal project from the Microsoft Patterns and Practices team. I was young and rather new to programming, it was my first “REAL” line of business app and I was very excited to just be making money doing what I loved.

    Most of the code base was not very good by my standards today, but it suited our team well enough. The project had all the classic problems that largely don’t exist in the .NET ecosystem anymore (at least I hope). It used all of the evil things that folks in the Alt.NET community we’re trying to fight against.

    • Stored procedures
    • Heavy use of inheritence
    • No tests
    • Shared developement database
    • Magic strings everywhere
    • Click once deployements from a developers workstation
    • TFS

    We were a small team lead by the quintessential expert beginner, but at the time I loved it. We were still doing a lot of good things, but I strived for more. I was a furocious seeker of knowledge, it was a GREAT time to be a .NET developer. There were so many of us with the same shared pain, I read every blog post I could get my hands on. I’d refresh CodeBetter, Los techies, etc all day. Enevitably I landed at the legendary blog of Jeremy D Miller, the shade tree developer. The Build Own CAB Series spoke directly to my pains at work. I’m not sure if anyone else at the time fought so hard against stored procedures. His twitter debates about them were legendary. I think back on how badly he was attacked on twitter about it and it most have been hard, others too, it was a hard hard hard fight. At the time I didn’t fully understand the problems, but I certainly benefited from that effort. His efforts truly spoke to me, I told myself “I want to work at Dovetail someday, I want to learn from the best.”

    This lead me to attend Pablo’s Fiesta in February 2010. I remember getting off of the plane and renting a car to go down to the Holiday Inn near the conference. There I ran into Dru Sellers and he says, “Hey, would you like to join me for lunch, I’m going to Chuy’s with my good friend Jeremy Miller”. I was blown away, hells yeah I want to come with you. Afterward he said invited me to join him on a visit to the Dovetail offices, it was like a dream come true, I was there when Chad Myers, Jeremy Miller, and Josh Flanagan were giving Phil Haack the very first demo of FubuMVC. I was there to see Dru give the first demo of Nubular (which inspired NuGet). It was there I met Ryan Kelley and we became known as “The two Ryan’s

    In 2011 I went to work for Dovetail and I had achieved my goal. I finally made it and for two great years I got to work with my programming heros Jeremy Miller, Chad Myers, and Dru Sellers. In those two years me and the rest of the FubuMVC team built some awesome truly innovative things, we tried to boil the ocean so to speak.

    • Bottles
    • Command line parsing with model binding
    • Multi view engine support
    • Controllers without base classes
    • IoC all the way down
    • Html conventions
    • Content negotiation

    In 2013, I left Dovetail, moved to ruby and formed my company HuBoard, Inc but I’ve always kept my eye on the community and I’m still friends with many people in the community.

    Why all this history, get the point man

    I tell you all the history, because that was the theme of #DotNetFringe in a large part. There was a lot of history in that conference ballroom. The people there were the people that fought tooth and nail to get MSFT to embrace open source.

    There was a panel where a lot of that history came out, I’ll have to go back and watch some of the videos but the “shocker” of the panel was when Scott Hanselman called James Nugent.

    Elitist and mean

    That will be a moment in the history of open source in .NET for me. It’s history just like the time Rob Conery called the Alt.NET community a bunch of assholes on stage. There are a lot of sins that Microsoft has not atoned for and to put it bluntly there are a lot of mean people in the community that need to be nicer.

    All of that history, is all the drama history it’s just the past so to speak, it’s meaningless in the grand scheme of the true historic moment. We won!

    Microsoft open sourced .NET

    The real history is:

    That is the only history that matters. There are a lot of people that are afraid of the past and they shouldn’t be. It’s open source now and there is no going back.

    Point of no return

    They have reached the point of no return, Microsoft has a hard task ahead but if they mess it up, the community will fork it. It happened to NodeJS with io.js and it seems to be working just fine for them. I don’t want that to happen, but open source will win.

    I for one am excited and any other current or former .NET developers should be too. Java is a huge disappointment if you move over from a strong C# background. There are problems that compiled, strongly typed, fast as hell languages are really really good at and I can wait for .NET versions of java technologies like Apache Storm and Hadoop.

    Things I’m excited for:

    • static linking the CLR
    • apt-get install CLR
    • CLR support for languages on the JVM
      • clojure.NET
      • scala.NET
      • ruby.NET
      • etc
    • .NET + Linux == Happiness
      • vagrant
      • ssh
      • debian packages
      • FastCGI (nginx)
    • Storm.NET
    • Hadoop.NET

    Things you should expect and thus not be mean to people about:

    • Tooling that doesn’t support windows
      • version managers like rbenv, rvm, nodenv for .NET
    • Noobies
    • former .NETers returning
    • Java converts

    It’s my opinion that the CLR is better than the JVM and hopefully Microsoft handles .NET better than Oracle has handled the JVM.

    For me, I’m letting the past go.

    I’ve realized I have a chip on my shoulder too and DotNetFringe was the therapy I needed to get over it and let it go.

    In closing, don’t be mean, the drama history is not history it’s just the past.

    Cheers’s to the true history, .NET is open source!

    Cheers’s to the future, I can’t wait for the next DotNetFringe

    How to *easily* apt-get update “offline”


    Building a Virtual Appliance is hard.

    A virtual appliance is a pre-configured virtual machine image, ready to run on a hypervisor; virtual appliances are a subset of the broader class of software appliances. Installation of a software appliance on a virtual machine and packaging that into an image creates a virtual appliance. Like software appliances, virtual appliances are intended to eliminate the installation, configuration and maintenance costs associated with running complex stacks of software.

    via Wikipedia

    Put simply, a virtual applicance is a VM export (typically in OVF via an ova file) with your application pre-installed on it. Sometimes thats enough to sell your product or install your product on-premises, but to truely deliver on the above promise of eliminating installation, configuration, and maintenace costs you need to provide a few basic things.

    1. Basic network configuration
    2. User interface to input configuration
    3. Upload endpoint for updates
    4. Disconnected Installation/Provisioning process (No internet!)

    By far the most challenging part of building a virtual applicance is provisioning the machine while disconnected from the internet. It’s easy to take for granted the ease and simplicity of apt-get install and as it turns out, “offline” apt support isn’t a trivial endevour.

    Like most things in linux there are many many many many ways to skin a cat but I think I’ve found a strong solution.

    First step, you need a clean distro of linux. You need to start with a machine image that effectively a fresh install from an iso.

    Install apt-cacher-ng before you install anything else

    apt-get install apt-cacher-ng

    Next you’ll need to configure apt to use apt-cacher-ng as a proxy server.

    echo 'Acquire::http { Proxy "http://localhost:3142"; };' > /etc/apt/apt.conf.d/02Proxy

    Now you can provision your machine as would normally, personally we use puppet.

    puppet apply

    Next you’ll want to package up the apt-cacher-ng cache folder that’s located at /var/cache/apt-cacher-ng. For HuBoard:Enterprise we use debian files as our “file delivery” mechanism. We have a simple rake script that uses fpm to build a debian package containing the cache folder, but a tarball will work just as well.

    Ok, so now you have a base line, you’ve effectively built a 1.0 version of every debian package installed on your image. It’s important understand the concept of your “offline” image and your “online” image. The offline image is the Virtual Appliance that your have given to your customer your online image is your snapshot of the image before you exported it to the OVF. Here is where it can get a little tricky, let imagine that you’ve released 1.0 of your amazing product… Let’s call it HuBoard:Enterprise 😉

    You shipped your customers the MVP to get your application to work and update, but your getting flooded with support requests that your customers would like to monitor the resources your Appliance is using, so you’re like “I know, I’ll use collectd”. Here is how you would do it.

    First step, on your online machine, install collectd like you normally would.

    apt-get install collectd

    Next package up the apt-cacher-ng folder

    rake package

    Now upload the cached folder to your offline machine and install it right over top the baseline cache folder. If your using debian like we are…

    dpkg -i apt-cacher-offline_1.0.1_all.deb<br /> chown -R apt-cacher-ng:apt-cacher-ng /var/cache/apt-cacher-ng

    Now what’s really cool about this approach is that apt-cacher-ng builds legit bonifide apt repositories so you can point your sources.list directly at your /var/cache folder.

    Here’s an example

    Ok now that you’ve pointed your offline machine at your local file system, you can install the missing packages extremely easy

    apt-get update<br /> apt-get install collectd

    Here’s a link to the GitHub gist if you prefer to read the post in gist form.

    • Ryan

    How to hire a freelance designer


    I’ve had a lot of requests to write a blog post about my experience working with a freelance designer on HuBoard.

    Going into the project I only knew one thing, I need a logo and some branding help.

    Things I now know after this project:

    1. How much do freelance designers charge?
    2. How do you find an experienced designer?
    3. How do you task designers with work?
    4. What experience should you expect from a designer?

    1. How much do freelance designers charge?

    This was the first thing that was very hard for me. It seemed clouded in mystery. How much is a brand worth? This is such an ambiguous question, the right brand could be priceless. Your designer might be creating to next Nike, Twitter, or GitHub brand and walking away with $35 has got to suck.

    I started asking questions and I contacted the only designer that I know and asked. Here’s what I found out.

    Here is a super rough breakdown of time and cost (potential). This will get you to the MVP.

    1. Branding exercise / messaging – 25hrs
    2. Home / marketing page design (no copy writing) – 35hrs
    3. Email templates for MailChimp (2 templates) – 10hrs

    Each item will have 2 revisions – First attempt and final. Total time – 70hrs

    Rates will vary depending on talent. You can easily find someone with less design experience at a rate of $20 – $40hr but you’ll take a hit on quality. This may not be a problem for an MVP. Startups often make the mistake of thinking things need to be perfect in order to ship. Not true.

    On the flip side, you can hire a more experienced designer at a rate of $65 – $85hr. This option puts you in the public eye and creates a buzz around a product. The finer details of the application are precise and user experience is memorable.

    Ok, ~70 hrs @ $70/hr = ~$5000 for an experienced designer. I don’t have $5k just laying around to pay a designer, but I really wanted a solid vision and focus moving forward. So I decided to cut back, the most important thing for me was the Brand and logo. 25-30 hours @ $70 = ~$2000 dollars, now I had my budget.

    2. How do you find an experienced designer?

    Armed with a budget I set out to find my designer. There are some crowd source sites there were recommended to me like elance and 99designs. I have nothing against those sites, but that wasn’t the experience I was looking for. I wanted a more personal experience.

    Another option is legit design firms. I inquired with Dog Dive Creative and a few others. I’m sure they are fantastic and professional, unfortunately they were outside of my budget.

    I think Andre is fantastic and I have a friendly relationship with him already so I told him what my budget was and asked if he was interested. He was booked up and unavailable so he helped me out by posting my inquiry on juiiicy which is a job posting community for freelance designers.

    Their process is described as:

    1. Designers ask for an introduction
    2. Pick the designer you want
    3. Pay them through juiiicy

    And my experience was exactly as described. Around 10 designers posted that they were interested and juiiicy provided me links to their portfolios on dribbble. I felt like a a pretty princess, all these amazing designers all asking for my hand in holy freelance-tromony.

    It was a tough decision everyone was sooo good, I eventually chose the young and super talented Andrew Colin Beck. A true scholarly gentlemen that was about to graduate from design school at BYU.

    3. How do you task designers with work?

    My relationship with Andrew started with introductions and messages back a forth with juiiicy. We set a google hangout and had a call to talk about the project. We discussed what my budget was and the scope of work that I needed done.

    I came to the meeting prepare with a list of about 8 things I wanted done listed in order of priority.

    1. Logo
    2. Brand guidelines
    3. Recommendations on colors, typography, look and feel
    4. Assets to represent Ready/Blocked/Done states
    5. Home page, pricing page, and error splash pages
    6. Email templates, letter heads, and print based branding

    We agreed on the scope of the project and how much work we could get done with the budget that I had. We both agreed that we could get through 1-4.

    I learned that standard practice with freelance designers is to pay 50% up front and the final 50% upon delivery. Andrew set me an invoice and things were set in motion.

    4. What experience should you expect from a designer?

    Once Andrew received payment we had a short brainstorming call so Andrew could what the project was about and what I wanted from the brand. Armed with knowledge Andrew went and brainstormed and came back with some ideas.

    ideas

    We talked about some rough ideas he had for the brand and I narrowed it down to two chooses that I wanted to see fleshed out a little bit more. Number 1 and number 4, an owl mascot or this collaborative logo that he was really excited about. So he went off to explore those ideas further.

    option 1

    We discussed the option of the owl and I liked it but it was far from original. Having an owl mascot has been done before and its hard to make it “your” brand.

    option 2

    Option 2 was interesting and Andrew was really excited about it. This idea of people collaborating to create the logo. It was risky but he was really excited so I went with it.

    final logo

    After deciding to go with option 2 he explored it further and came up with the final version. Overall I’m very happy with the static version of the logo. He still liked this idea of an animated logo and wanted to explore it in different ways.

    animated

    The concept is to animate an interesting statistic into the logo. Overall I think its a bit risky to mess with your brand in a strange way, but why not I’m ok with a little risk. I don’t expect to get everything right the first time.

    Tell me what you think. Love it? Hate it? Similar experiences? Want more posts like this?

    EmberJS :: Beyond the tutorials


    How I got started with EmberJS

    I’ve been trying to learn and get better at writing good idomatic EmberJS code. At times I’ve been extremely frustrated and at other times I’ve been extremely delighted.

    For the past 3-4 years ago I’ve been involved and contributed to an alternate .NET web framework called FubuMVC. Much like EmberJS, FubuMVC was an ambitious project that was primary spearheaded by a few huge open source contributors in .NET. I believed it was a better way and that learning to use the framework would make me a better developer.

    But not all of these last 4 years have been what I would consider fun. In a lot of respect pinning down a stable version of the framework was frustrating. Things moved fast and API’s changed often leaving you on a treadmill of upgrading the framework and fixing bugs.

    It’s for these reasons that I avoided EmberJS for a long time, but after spending some time at my local emberjs meetup. I was told that things had gotten a lot better after the huge router rewrite and that Tom Dale and company had started getting much better about backward compatibility. I’m not sure how I get attracted to these types of frameworks but I figured I’d jump in.

    With that I’ll give you the first thing I did

    Joined a local user group

    The emberjs meetup is by far my favorite meetup group in Austin. It’s really diverse, there’s a retired 60+ year that got super excited when he got the TodoMVC app working with ember. It’s exciting to hang out with everyone at the meetup and it gets me excited to keep coming back and sharing what I’ve learned. I went to the meetup for the free beer and conversation and stayed for the awesome people and surprisingly awesome framework.

    Super duper level 1 beginner stuff

    Read the doc and getting started guide

    The next thing I did was I browsed through some of the material on the homepage, but I’m a pretty smart guy and I’ve been around the block a few times. It didn’t take me long to understand the patterns and I quickly needed more than just a two or three page explanation of how to create a home page, about page and link them with outlets and link-to helpers.

    Free course on tutsplus

    Next thing I did was watch the series on tutsplus. It’s a great video series but still left me wanted a little bit more complex concepts.

    Ember101.com

    Not a lot of videos but I kept coming back to it from time to time for a refresher on basic syntax things.

    Heretical Guide To Ember JS

    Heretical Guide To Ember JS was good. It was a quick read, but it was still very beginner. In the final chess game example, it was helpful to see something a bit out of the ordinary written in ember. I liked how it wasn’t route heavy but still had a few controllers and views. It was still super basic and ultimately not what I was looking for. If you don’t understand what the jankly named classes of the Model+View+Controller+Template+Router (or whatever the hell its called) pattern are supposed to do in Ember this is the book for you. He explains the architecture really really well.

    Peepcode Fire Up Ember (yes I really did watch and read all this stuff)

    Fire up ember was again… very basic, it doesn’t go much further than the free tutsplus course but it’s great for getting started.

    What helped me get to level 2, not so beginner nooby shit

    Lurking the #emberjs freenode channel

    I lurked the IRC channel for weeks, looked at the crazy questions people were asking and inspected their jsbin snippets that they would send in. It can be a little overwhelming and you’re not going to see immediate benefit from it but it help me pick up a few things like

    • How and when to use a CollectionView and what that does for you

    • Upcoming api tweeks a changes for the link-to helpers

    Read the source code noob

    TravisCI and Discource kind of helped but I still have little to no clue whats going on in those code bases. I know they are doing some non-trival things but it a little to much work for a noob to look at the code or get the apps in a running state to debug any of it.

    The emberjs source docs are awesome and I constantly reference them.

    Nerdyworm‘s github account and blog helped me a lot to do simple things like open dialogs. That’s where it finally clicked for me on how to pass models around your application using actions

    K now what

    So it’s been about 4 months and I still feel like a beginner in the framework. Maybe it’s because I knew FubuMVC so well, I’ve read almost all of the source code and helped write and fix bugs in a big chuck of it. I like having that kind of deep knowledge of a framework. EmberJS is pretty big and the way its organized isn’t familiar to me yet. It’s not like BackboneJS where you can read the entire source code in about an hour.

    I think the investment will be worth it. I’ve used a lot of client-side tech (backbone, knockout, requirejs) and ember the best I’ve used so far. They have solved a lot of the pain points and things that bothered me about (Single Page Apps). It feels like all of the binding power of knockout without the code organization and scoping problems. It really cuts down a lot of the ceremony that I was experiencing with backbone.

    Climbing the green mountain


    How to defeat the evil green mountain

    what is the green mountain?

    A picture is worth a thousand words. The green mountain is the response time graph from huboard. From time to time the GitHub api hangs and takes a really long time to return data which causes huge spikes in the response time for huboard. I recently release some fixes to huboard to help mitigate this problem.

    Ways to fix the green mountain

    First and easiest method is to add a retry mechanism in your api library and set a low timeout for your requests. Since huboard uses the api library ghee, which I happen to know very well since I wrote it. It was really easy.

    Ghee uses faraday it was very easy to configure ghee to retry requests it was also quite easy to add some middleware into faraday to set the timeout to 1 second

    The second way is to cache responses from the github api. This turned out to be a bit of a technical challenge. The GitHub has support for making conditional requests the send an e-tag header with every request made. The twist is that you need to return that e-tag in your next api call to see if anything has changed. It gets complicated when you make several api requests within a single request you have to cache the entire request and then pull out the cached request to get the last e-tag and then send a request to GitHub to see if anything has changed. Thankfully it wasn’t too hard to implement with ghee. Yeah for the power of middleware

    So far so good, there are still spikes but you will notice that the legend on the left side is much lower, going from spikes up to 3 seconds to every request being under a second! Thats a pretty big win for me and the users of course.

    Happy huboarding!!!

    The 30 Second Standup


    The 30 Second Standup

    Every team should continuously challenge dogmatic practices and reflect on their value.

    Well written! Thanks Matt

    Huboard – socket.io backend


    huboard.com!

    Your favorite open source kanban board built on top of the GitHub api has gotten a little bit more awesome. I’ve decided to release the socket.io backend that is keeping huboard.com all up to date and super fancy.

    Why nodejs (express + socket.io)?

    1. Simple

    Nodejs (express + socket.io) was by far the easiest thing for me to get running on heroku. The original version I published back in August was only about 26 lines of javascript.

    2. Reliable

    I’m absolutely amazed, I pushed that code up to heroku back in August and haven’t touched it. It ran for about 6 months and I never even checked on it. As far as I can tell it never even crashed.

    3. Documentation

    There is a ton of great tutorials and blog posts just a google search away on getting socket.io up and running on heroku.

    4. Cheap (free as in beer)

    Plain vanilla install of socket.io and express configured to use xhr-polling has been running problem free on heroku with one free dyno for 6 months. I’m not exactly sure how far that will scale using the MemoryStore (eventually I’ll run out of RAM) but huboard is sitting at nearly 4000 users and haven’t had any problems yet

    Other options

    I did explore other options

    • .NET – server sent events – (fubumvc or signalr)

      • FubuMVC.ServerSentEvents
        • Not supported by heroku (see #1 && #4)
        • SSE only – no fallback support
        • Very little documentation
      • [SignalR]
        • Not supported by heroku
        • Not as stable (at the time)
    • Ruby – EventMachine

      • I originally wanted to use something simple on top of ruby. I tried several different approaches and ultimately I just couldn’t figure out how to host a HTTP POST endpoint && the socket connections inside the same app.

    Why did it take you so long to release it?

    Well the TL;DR; is that it wasn’t secure. It was largely experimental. Back when I published the RealTime™ support the socket.io server was only about 26 lines of nodejs. It had no security because I didn’t know how to set it up. So I secure it the best I could (with a simple correlation string) and called it a day. The code wasn’t published so hackers at least had a harder time connecting to the socket than if the code was in the open. Security by obscurity (I know shame on me, I’m sorry).

    How are you going to make it secure?

    Step one is open sourcing it, people who are using huboard and know more than I do will hopefully care enough to help me make it as secure as possible.

    • Things I’m doing already.

    All my users trust me with their OAuth token and I take that responsibility seriously. I try my best to make sure your authorization key doesn’t get into the wrong hands. Huboard never exposes your authorization key unencrypted.

    The socket server now performs a handshake when making a connection. Your encrypted OAuth token is passed into socket.io via query string, which turns around and passes the encrypted key to a huboard api endpoint that decrypts your token and asks the GitHub api if its valid.

    *** Disclaimer *** I’m not a security expert, I’m not happy that I’m passing the token back to huboard for verification. It doesn’t feel right. I tried for days to decrypt the token using node in order to validate the token inside the handshake code but I just ran into dead ends. I think it has something to do with the encoding of ruby vs node but I had very similar encryption code on both platforms and could figure out how to get node to decrypt a string from ruby or visa versa. Please help!

    Huboard redesign (gif edition)


    Huboard!

    I’ve been working on a big redesign of huboard since around September or so. I’m finally ready to reveal all of that hard work. So what better way to show off all the new features than animated gifs, right?

    Overall “cleaner” design and improved performance

    drag and drop

    Three state multi faceted filtering

    advanced_filtering

    Clicking a filter once will dim out issues that don’t match, click again and they disappear. Filters can be mixed and matched to help you narrow down exactly what you need to work on.

    Super fast text search on the title

    type_search

    Drag to assign issues

    assign issue

    Quickly assign issues by dragging your avatar onto the card

    Drag cards between milestones

    backlog view

    I built a whole new board to help you manage your backlog. Everything works exactly the same except the columns are now milestones.

    Prioritize milestones

    milestone reorder

    Ordering milestones using drag and drop makes it quick and easy to prioritize your backlog.

    That’s it. I hope y’all enjoy the new features

    UX: Color is only meaningful if it’s different


    Color is important for UX

    On the daily, when I’m not lurking imgur, I work on HR case management software. HR case management isn’t exactly the sexiest technology sector, but that doesn’t keep me from trying to provide the best User Experience possible.

    Recently I’ve been working on a light switch type control that can use various colors when they are in the active state

    Here’s the toggle color for making a case “Sensitive” in our application. A “Sensitive” case is restricted and can not be seen by users in our public facing application.

    Here’s what it looks like in the app on our case page.

    The color helps the user see from a glance that this Case is different, because the color isn’t the normal state. That’s the key here, it wouldn’t be special if it was always yellow.

    The main purpose of our application is to keep track of the entire conversation over the issue. This mostly consists of comments and emails between the employee and their HR rep.

    Notice here that I used the same control and color for toggling comments. If the HR rep doesn’t want a comment or an email to be seen by the employee they can set it to Internal. The logs are created as public so the color is meaningful because it’s different.

    Some customers want every log (comment, email, ect.) to be internal by default, so we have a setting they can configure that will create all of the logs as internal. This changes the meaning of the color, if everything is internal by default then the yellow color is no longer special it’s not different so it’s no longer meaningful.

    When that happens the control changes to “Public” and the color changes from a warning color to a primary color.

    Color is great in an application, but it’s only meaningful if it stands out from what’s normal in your application.

    Keep herpin` while you’re derpin`

    -Ryan

    Huboard goes RealTime ™


    Huboard!

    milestone ordering

    Websockets FTW I’ve recently added websocket support to the issues board in

    Huboard.

    Things I’ve added:

    • Live update for cards moving from column to column
    • Live update of issues closed from huboard.com
    • Live update of issues closed from github.com
    • Live update of issues opened from github.com

    Things still to come:

    • Live update of issues reopened from github.com
    • Live update of issues reordered from huboard.com
    • Live update of milestones reordered from huboard.com
    • Live update of milestones added from huboard.com That’s it for now, please enjoy using huboard F5 free!

subscribe via RSS